Path: blob/master/modules/auxiliary/admin/aws/aws_launch_instances.rb
19758 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'metasploit/framework/aws/client'67class MetasploitModule < Msf::Auxiliary8include Metasploit::Framework::Aws::Client910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Launches Hosts in AWS',15'Description' => %q{16This module will attempt to launch an AWS instances (hosts) in EC2.17},18'License' => MSF_LICENSE,19'Author' => [20'Javier Godinez <godinezj[at]gmail.com>',21],22'References' => [23['URL', 'https://drive.google.com/open?id=0B2Ka7F_6TetSNFdfbkI1cnJHUTQ'],24['URL', 'https://published-prd.lanyonevents.com/published/rsaus17/sessionsFiles/4721/IDY-W10-DevSecOps-on-the-Offense-Automating-Amazon-Web-Services-Account-Takeover.pdf']25],26'Notes' => {27'Stability' => [CRASH_SAFE],28'SideEffects' => [IOC_IN_LOGS],29'Reliability' => []30}31)32)33register_options(34[35OptString.new('AccessKeyId', [true, 'AWS access key', '']),36OptString.new('SecretAccessKey', [true, 'AWS secret key', '']),37OptString.new('Token', [false, 'AWS session token', '']),38OptString.new('RHOST', [true, 'AWS region specific EC2 endpoint', 'ec2.us-west-2.amazonaws.com']),39OptString.new('Region', [true, 'The default region', 'us-west-2' ]),40OptString.new('AMI_ID', [true, 'The Amazon Machine Image (AMI) ID', 'ami-1e299d7e']),41OptString.new('KEY_NAME', [true, 'The SSH key to be used for ec2-user', 'admin']),42OptString.new('SSH_PUB_KEY', [false, 'The public SSH key to be used for ec2-user, e.g., "ssh-rsa ABCDE..."', '']),43OptString.new('USERDATA_FILE', [false, 'The script that will be executed on start', 'tools/modules/aws-aggregator-userdata.sh'])44]45)46register_advanced_options(47[48OptPort.new('RPORT', [true, 'AWS EC2 Endpoint TCP Port', 443]),49OptBool.new('SSL', [true, 'AWS EC2 Endpoint SSL', true]),50OptString.new('INSTANCE_TYPE', [true, 'The instance type', 'm3.medium']),51OptString.new('ROLE_NAME', [false, 'The instance profile/role name', '']),52OptString.new('VPC_ID', [false, 'The EC2 VPC ID', '']),53OptString.new('SUBNET_ID', [false, 'The public subnet to use', '']),54OptString.new('SEC_GROUP_ID', [false, 'The EC2 security group to use', '']),55OptString.new('SEC_GROUP_CIDR', [true, 'EC2 security group network access CIDR', '0.0.0.0/0']),56OptString.new('SEC_GROUP_PORT', [true, 'EC2 security group network access PORT', 'tcp:22']),57OptString.new('SEC_GROUP_NAME', [false, 'Optional EC2 security group name', '']),58OptInt.new('MaxCount', [true, 'Maximum number of instances to launch', 1]),59OptInt.new('MinCount', [true, 'Minumum number of instances to launch', 1])60]61)62deregister_options('VHOST')63end6465def run66if datastore['AccessKeyId'].blank? || datastore['SecretAccessKey'].blank?67print_error('Both AccessKeyId and SecretAccessKey are required')68return69end70# setup creds for making IAM API calls71creds = {72'AccessKeyId' => datastore['AccessKeyId'],73'SecretAccessKey' => datastore['SecretAccessKey']74}75creds['Token'] = datastore['Token'] unless datastore['Token'].blank?7677create_keypair(creds) unless datastore['SSH_PUB_KEY'].blank?78vpc = datastore['VPC_ID'].blank? ? vpc(creds) : datastore['VPC_ID']79sg = datastore['SEC_GROUP_ID'].blank? ? create_sg(creds, vpc) : datastore['SEC_GROUP_ID']80subnet = datastore['SUBNET_ID'].blank? ? pub_subnet(creds, vpc) : datastore['SUBNET_ID']81unless subnet82print_error('Could not find a public subnet, please provide one')83return84end85instance_id = launch_instance(creds, subnet, sg)86action = 'DescribeInstances'87doc = call_ec2(creds, 'Action' => action, 'InstanceId.1' => instance_id)88doc = print_results(doc, action)89begin90# need a better parser so we can avoid shit like this91ip = doc['reservationSet']['item']['instancesSet']['item']['networkInterfaceSet']['item']['privateIpAddressesSet']['item']['association']['publicIp']92print_status("Instance #{instance_id} has IP address #{ip}")93rescue NoMethodError94print_error('Could not retrieve instance IP address')95end96end9798def opts(action, subnet, sg)99opts = {100'Action' => action,101'ImageId' => datastore['AMI_ID'],102'KeyName' => datastore['KEY_NAME'],103'InstanceType' => datastore['INSTANCE_TYPE'],104'NetworkInterface.1.SubnetId' => subnet,105'NetworkInterface.1.SecurityGroupId.1' => sg,106'MinCount' => datastore['MinCount'].to_s,107'MaxCount' => datastore['MaxCount'].to_s,108'NetworkInterface.1.AssociatePublicIpAddress' => 'true',109'NetworkInterface.1.DeviceIndex' => '0'110}111opts['IamInstanceProfile.Name'] = datastore['ROLE_NAME'] unless datastore['ROLE_NAME'].blank?112113fname = datastore['USERDATA_FILE']114unless fname.blank?115if File.exist?(fname)116opts['UserData'] = URI::DEFAULT_PARSER.escape(Base64.encode64(::File.read(fname, mode: 'r').strip))117else118print_error("Could not open userdata file: #{fname}")119end120end121opts122end123124def launch_instance(creds, subnet, sg)125action = 'RunInstances'126print_status("Launching instance(s) in #{datastore['Region']}, AMI: #{datastore['AMI_ID']}, key pair name: #{datastore['KEY_NAME']}, security group: #{sg}, subnet ID: #{subnet}")127doc = call_ec2(creds, opts(action, subnet, sg))128doc = print_results(doc, action)129return if doc.nil?130131# TODO: account for multiple instances132if doc['instancesSet']['item'].instance_of?(Array)133instance_id = doc['instancesSet']['item'].first['instanceId']134else135instance_id = doc['instancesSet']['item']['instanceId']136end137print_status("Launched instance #{instance_id} in #{datastore['Region']} account #{doc['ownerId']}")138action = 'DescribeInstanceStatus'139loop do140sleep(15)141doc = call_ec2(creds, 'Action' => action, 'InstanceId' => instance_id)142doc = print_results(doc, action)143if doc['instanceStatusSet'].nil?144print_error('Error, could not get instance status, instance possibly terminated')145break146end147status = doc['instanceStatusSet']['item']['systemStatus']['status']148print_status("instance #{instance_id} status: #{status}")149break if status == 'ok' || status != 'initializing'150end151instance_id152end153154def create_keypair(creds)155action = 'ImportKeyPair'156doc = call_ec2(creds, 'Action' => action, 'KeyName' => datastore['KEY_NAME'], 'PublicKeyMaterial' => Rex::Text.encode_base64(datastore['SSH_PUB_KEY']))157if doc['Response'].nil?158doc = print_results(doc, action)159if doc['keyName'].nil? || doc['keyFingerprint'].nil?160print_error('Error creating key using provided key material (SSH_PUB_KEY)')161else162print_status("Created #{doc['keyName']} (#{doc['keyFingerprint']})")163end164elsif doc['Response']['Errors'] && doc['Response']['Errors']['Error']165print_error(doc['Response']['Errors']['Error']['Message'])166else167print_error('Error creating key using provided key material (SSH_PUB_KEY)')168end169end170171def pub_subnet(creds, vpc_id)172# First look for subnets that are configured to provision a public IP when instances are launched173action = 'DescribeSubnets'174doc = call_ec2(creds, 'Action' => action)175doc = print_results(doc, action)176vpc_subnets = doc['subnetSet']['item'].select { |x| x['vpcId'] == vpc_id }177pub_subnets = vpc_subnets.select { |x| x['mapPublicIpOnLaunch'] == 'true' }178return pub_subnets.first['subnetId'] if pub_subnets.count > 0179180# Second, try to retrieve public subnet id by looking through route tables to find subnets181# associated with an Internet gateway182action = 'DescribeRouteTables'183doc = call_ec2(creds, 'Action' => action)184doc = print_results(doc, action)185vpc_route_table = doc['routeTableSet']['item'].select { |x| x['vpcId'] == vpc_id }186vpc_route_table.each do |route_table|187next if route_table['associationSet'].nil? || route_table['routeSet'].nil?188189entries = route_table['routeSet']['item']190if entries.instance_of?(Hash)191if entries['gatewayId'].start_with?('igw-')192return route_table['associationSet']['item'].first['subnetId']193end194else195route_table['routeSet']['item'].each do |route|196if route['gatewayId'] && route['gatewayId'].start_with?('igw-')197return route_table['associationSet']['item'].first['subnetId']198end199end200end201end202nil203end204205def create_sg(creds, vpc_id)206name = Rex::Text.rand_text_alphanumeric(8)207action = 'CreateSecurityGroup'208doc = call_ec2(creds, 'Action' => action, 'GroupName' => name, 'VpcId' => vpc_id, 'GroupDescription' => name)209doc = print_results(doc, action)210print_error('Could not create SG') && return if doc['groupId'].nil?211212sg = doc['groupId']213proto, port = datastore['SEC_GROUP_PORT'].split(':')214cidr = URI::DEFAULT_PARSER.escape(datastore['SEC_GROUP_CIDR'])215action = 'AuthorizeSecurityGroupIngress'216doc = call_ec2(creds, 'Action' => action,217'IpPermissions.1.IpRanges.1.CidrIp' => cidr,218'IpPermissions.1.IpProtocol' => proto,219'IpPermissions.1.FromPort' => port,220'IpPermissions.1.ToPort' => port,221'GroupId' => sg)222doc = print_results(doc, action)223if doc['return'] && doc['return'] == 'true'224print_status("Created security group: #{sg}")225else226print_error('Failed creating security group')227end228sg229end230231def vpc(creds)232action = 'DescribeVpcs'233doc = call_ec2(creds, 'Action' => action)234doc = print_results(doc, action)235if doc['vpcSet'].nil? || doc['vpcSet']['item'].nil?236print_error("Could not determine VPC ID for #{datastore['AccessKeyId']} in #{datastore['RHOST']}")237return nil238end239item = doc['vpcSet']['item']240return item['vpcId'] if item.instance_of?(Hash)241return item.first['vpcId'] if item.instance_of?(Array) && !item.first['vpcId'].nil?242243print_error("Could not determine VPC ID for #{datastore['AccessKeyId']} in #{datastore['RHOST']}")244nil245end246end247248249