Initial checkin of launch instances module

bug/bundler_fix
Javier Godinez 2017-03-12 22:57:58 -07:00
parent 06e6a973ce
commit 26d344a0ef
2 changed files with 406 additions and 0 deletions

View File

@ -0,0 +1,166 @@
# Introduction
The Launch Instances module (`aws_launch_instances`) can be used to launch a
Cloud host with a public IP address. Although hosts can be launched using the
Web console or the CLI, launching a host in the Cloud requires a fair
amount of configuration; this module does its best to abstract configuration
requirements away from the user by auto detecting the VPC, subnets, creating
security groups, etc.
# Background
## AWS API Access Keys
API access keys can be used to make calls against the AWS API, to say
retrieve deployment packages from S3.
## VPC
The VPC or Virtual Private Cloud, an isolated local area network. Network access
can be made available by assigning an Internet routable IP address to a host or
routing traffic to it through an ELB (Elastic Load Balancer). In either case
security-groups are used to open access to network ranges and specific TPC/UDP
ports. Security-groups provide much of the functionality of traditional firewalls
and can be configured by specifyig a protocol, a CIDR and a port.
## How it Works
The module performs several tasks to launch a host with a public IP address,
these are as follow: 1) select a VPC, 2) select a subnet, 3) create/select a
security group, 4) create/select a key-pair, and 5) launch a host.
The module will attempt to launch the host in the first VPC it finds in the
given region (`Region` option). Most of the time there is only one VPC per
account per region, however one might find multiple VPCs within the same region.
In this case, one may use the `VPC_ID` advanced option to specify the VPC to
use. Selecting a subnet is a bit more complicated. To have traffic routed
between us and the Cloud host, a public subnet (a subnet that is routable to an
Internet gateway) must be selected and the Cloud host must be associated with
an Internet routable IP address. The module dynamically finds which subnet to
launch the host in. It will use the first subnet it finds having the
`Auto-assign Public IP` option set, if no such subnet exists, then it will
select the first subnet having an Internet gateway. To circumvent this process,
the `SUBNET_ID` advanced option can be set.
When launching a Cloud host at least one security group is required. There are
several advanced options for creating/selecting a security group. The
`SEC_GROUP_ID` option works much in the same way the `VPC_ID` option does.
That is, the module will create a security group unless the `SEC_GROUP_ID`
options is set. If the `SEC_GROUP_ID` option is not set, the module will attempt
to create a security group using the values specified in the `SEC_GROUP_CIDR`,
`SEC_GROUP_NAME`, and `SEC_GROUP_PORT` options as configuration.
The `KEY_NAME` and `SSH_PUB_KEY` options are used in conjunction to select or
create a key-pair (a named SSH public key). Key-pairs are used to authenticate
to the host once it is running. The `KEY_NAME` defaults to admin while
`SSH_PUB_KEY` is optional. If the `SSH_PUB_KEY` is left unset, then the module
will not attempt to create a key-pair and will simply attempt to launch the
instance using an existing key-pair denoted by `KEY_NAME`. To set the
`SSH_PUB_KEY` option, a public SSH key must be provided as can be generated by
`ssh-keygen -y -f <private key filename>`. Once a key-pair is created/selected,
the module launches the host via the AWS API specifying that it should
associate a public IP address.
## Options
The Launch Instances module is an auxiliary module that can be loaded using the
use command. To run the module, only the `AccessKeyId`, `SecretAccessKey`, and
`KEY_NAME` options are required.
Basic Options:
* `AMI_ID`: The Amazon Machine Image (AMI) ID (region dependent)
* `RHOST`: the AWS EC2 Endpoint (ec2.us-west-2.amazonaws.com), may change this to something closer to you
* `Region`: The default region (us-west-2), must match endpoint
* `AccessKeyId`: AWS API access key
* `SecretAccessKey`: AWS API secret access key
* `Token`: AWS API session token, optional
* `KEY_NAME`: The SSH key to be used for ec2-user
* `SSH_PUB_KEY`: The public SSH key to be used for ec2-user, e.g., "ssh-rsa ABCDE..."
* `USERDATA_FILE`: The script that will be executed on start
Advanced Options:
* `INSTANCE_TYPE`: The instance type
* `MaxCount`: Maximum number of instances to launch
* `MinCount`: Minumum number of instances to launch
* `ROLE_NAME`: The instance profile/role name
* `RPORT:` AWS EC2 Endpoint TCP Port
* `SEC_GROUP_ID`: the EC2 security group to use
* `SEC_GROUP_CIDR`: the EC2 security group network access CIDR, defaults to 0.0.0.0/0
* `SEC_GROUP_NAME`: the EC2 security group name
* `SEC_GROUP_PORT`: the EC2 security group network access port, defaults to tcp:22
* `SUBNET_ID`: The public subnet to use
* `UserAgent`: The User-Agent header to use for all requests
* `VPC_ID`: The EC2 VPC ID
# Usage
The Launch Instances module is an auxiliary module that can be loaded using the
use command. To run the module, only the `AccessKeyId`, `SecretAccessKey`, and
`KEY_NAME` options are required.
```
msf > use auxiliary/admin/aws/aws_launch_instances
msf auxiliary(aws_launch_instances) > show options
Module options (auxiliary/admin/aws/aws_launch_instances):
Name Current Setting Required Description
---- --------------- -------- -----------
AMI_ID ami-1e299d7e yes The Amazon Machine Image (AMI) ID
AccessKeyId yes AWS access key
KEY_NAME admin yes The SSH key to be used for ec2-user
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOST ec2.us-west-2.amazonaws.com yes AWS region specific EC2 endpoint
Region us-west-2 yes The default region
SSH_PUB_KEY no The public SSH key to be used for ec2-user, e.g., "ssh-rsa ABCDE..."
SecretAccessKey yes AWS secret key
Token no AWS session token
USERDATA_FILE no The script that will be executed on start
msf auxiliary(aws_launch_instances) > set SecretAccessKey asdfasd+asdfasdfasd...
SecretAccessKey => asdfasd+asdfasdfasd...
msf auxiliary(aws_launch_instances) > set AccessKeyId AKIAAKIAAKIAAKIAAKIAA
AccessKeyId => AKIAJH47PFECK3EVTBKA
msf auxiliary(aws_launch_instances) > set KEY_NAME ec2-user-key
KEY_NAME => ec2-user-key
msf auxiliary(aws_launch_instances) > set SSH_PUB_KEY ssh-rsa ABCDEDG123...
SSH_PUB_KEY => ssh-rsa ABCDEDG123...
msf auxiliary(aws_launch_instances) > run
[*] Created ec2-user-key (ab:cd:ef:12:34:56:78:90:ab:ac:ad:ab:a1:23:45:67)
[*] Created security group: sg-12345678
[*] Launching instance(s) in us-west-2, AMI: ami-1e299d7e, key pair name: ec2-user, security group: sg-12345678, subnet ID: subnet-abcdefgh
[*] Launched instance i-12345678 in us-west-2 account 123456789012
[*] instance i-12345678 status: initializing
[*] instance i-12345678 status: initializing
...
[*] instance i-12345678 status: ok
[*] Instance i-12345678 has IP address 54.186.158.6
[*] Auxiliary module execution completed
```
When the host has passed its primary system checks, the IP address will be
displayed. We can use this IP address to SSH to the host. Please note that
most users will want to set the `SEC_GROUP_CIDR` option to restrict access to
our new Cloud host.
To SSH into the host, you must specify the SSH key, and ec2-user username, e.g.,
```
$ ssh -i ec2-user-key ec2-user@54.186.158.6
The authenticity of host '54.186.158.6 (54.186.158.6)' can't be established.
ECDSA key fingerprint is SHA256:ePj6WtCeK...
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '54.186.158.6' (ECDSA) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/
5 package(s) needed for security, out of 9 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-172-31-8-176 ~]$
```

View File

@ -0,0 +1,240 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'metasploit/framework/aws/client'
class MetasploitModule < Msf::Auxiliary
include Metasploit::Framework::Aws::Client
def initialize(info = {})
super(
update_info(
info,
'Name' => "Launches Hosts in AWS",
'Description' => %q{
This module will attempt to launch an AWS instances (hosts) in EC2.
},
'License' => MSF_LICENSE,
'Author' => [
'Javier Godinez <godinezj[at]gmail.com>',
],
'References' => [
[ 'URL', 'https://drive.google.com/open?id=0B2Ka7F_6TetSNFdfbkI1cnJHUTQ'],
[ 'URL', 'https://published-prd.lanyonevents.com/published/rsaus17/sessionsFiles/4721/IDY-W10-DevSecOps-on-the-Offense-Automating-Amazon-Web-Services-Account-Takeover.pdf' ]
]
)
)
register_options(
[
OptString.new('AccessKeyId', [true, 'AWS access key', '']),
OptString.new('SecretAccessKey', [true, 'AWS secret key', '']),
OptString.new('Token', [false, 'AWS session token', '']),
OptString.new('RHOST', [true, 'AWS region specific EC2 endpoint', 'ec2.us-west-2.amazonaws.com']),
OptString.new('Region', [true, 'The default region', 'us-west-2' ]),
OptString.new("AMI_ID", [true, 'The Amazon Machine Image (AMI) ID', 'ami-1e299d7e']),
OptString.new("KEY_NAME", [true, 'The SSH key to be used for ec2-user', 'admin']),
OptString.new("SSH_PUB_KEY", [false, 'The public SSH key to be used for ec2-user, e.g., "ssh-rsa ABCDE..."', '']),
OptString.new("USERDATA_FILE", [false, 'The script that will be executed on start', ''])
]
)
register_advanced_options(
[
OptString.new('RPORT', [true, 'AWS EC2 Endpoint TCP Port', 443]),
OptBool.new('SSL', [true, 'AWS EC2 Endpoint SSL', true]),
OptString.new('INSTANCE_TYPE', [true, 'The instance type', 'm3.medium']),
OptString.new('ROLE_NAME', [false, 'The instance profile/role name', '']),
OptString.new('VPC_ID', [false, 'The EC2 VPC ID', '']),
OptString.new('SUBNET_ID', [false, 'The public subnet to use', '']),
OptString.new('SEC_GROUP_ID', [false, 'The EC2 security group to use', '']),
OptString.new('SEC_GROUP_CIDR', [true, 'EC2 security group network access CIDR', '0.0.0.0/0']),
OptString.new('SEC_GROUP_PORT', [true, 'EC2 security group network access PORT', 'tcp:22']),
OptString.new('SEC_GROUP_NAME', [false, 'Optional EC2 security group name', '']),
OptInt.new('MaxCount', [true, 'Maximum number of instances to launch', 1]),
OptInt.new('MinCount', [true, 'Minumum number of instances to launch', 1])
]
)
deregister_options('VHOST')
end
def run
if datastore['AccessKeyId'].blank? || datastore['SecretAccessKey'].blank?
print_error("Both AccessKeyId and SecretAccessKey are required")
return
end
# setup creds for making IAM API calls
creds = {
'AccessKeyId' => datastore['AccessKeyId'],
'SecretAccessKey' => datastore['SecretAccessKey']
}
creds['Token'] = datastore['Token'] unless datastore['Token'].blank?
create_keypair(creds) unless datastore['SSH_PUB_KEY'].blank?
vpc = datastore['VPC_ID'].blank? ? vpc(creds) : datastore['VPC_ID']
sg = datastore['SEC_GROUP_ID'].blank? ? create_sg(creds, vpc) : datastore['SEC_GROUP_ID']
subnet = datastore['SUBNET_ID'].blank? ? pub_subnet(creds, vpc) : datastore['SUBNET_ID']
unless subnet
print_error("Could not find a public subnet, please provide one")
return
end
instance_id = launch_instance(creds, subnet, sg)
action = 'DescribeInstances'
doc = call_ec2(creds, 'Action' => action, 'InstanceId.1' => instance_id)
doc = print_results(doc, action)
begin
# need a better parser so we can avoid shit like this
ip = doc['reservationSet']['item']['instancesSet']['item']['networkInterfaceSet']['item']['privateIpAddressesSet']['item']['association']['publicIp']
print_status("Instance #{instance_id} has IP adrress #{ip}")
rescue NoMethodError
print_error("Could not retrieve instance IP address")
end
end
def opts(action, subnet, sg)
opts = {
'Action' => action,
'ImageId' => datastore['AMI_ID'],
'KeyName' => datastore['KEY_NAME'],
'InstanceType' => datastore['INSTANCE_TYPE'],
'NetworkInterface.1.SubnetId' => subnet,
'NetworkInterface.1.SecurityGroupId.1' => sg,
'MinCount' => datastore['MinCount'].to_s,
'MaxCount' => datastore['MaxCount'].to_s,
'NetworkInterface.1.AssociatePublicIpAddress' => 'true',
'NetworkInterface.1.DeviceIndex' => '0'
}
opts['IamInstanceProfile.Name'] = datastore['ROLE_NAME'] unless datastore['ROLE_NAME'].blank?
unless datastore['USERDATA_FILE'].blank?
if File.exist?(datastore['USERDATA_FILE'])
opts['UserData'] = Base64.encode64(open(datastore['USERDATA_FILE'], 'r').read).strip
else
print_error("Could not open userdata file: #{datastore['USERDATA_FILE']}")
end
end
opts
end
def launch_instance(creds, subnet, sg)
action = 'RunInstances'
print_status("Launching instance(s) in #{datastore['Region']}, AMI: #{datastore['AMI_ID']}, key pair name: #{datastore['KEY_NAME']}, security group: #{sg}, subnet ID: #{subnet}")
doc = call_ec2(creds, opts(action, subnet, sg))
doc = print_results(doc, action)
return if doc.nil?
# TODO: account for multiple instances
if doc['instancesSet']['item'].instance_of?(Array)
instance_id = doc['instancesSet']['item'].first['instanceId']
else
instance_id = doc['instancesSet']['item']['instanceId']
end
print_status("Launched instance #{instance_id} in #{datastore['Region']} account #{doc['ownerId']}")
action = 'DescribeInstanceStatus'
loop do
sleep(15)
doc = call_ec2(creds, 'Action' => action, 'InstanceId' => instance_id)
doc = print_results(doc, action)
if doc ['instanceStatusSet'].nil?
print_error("Error, could not get instance status, instance possibly terminated")
break
end
status = doc['instanceStatusSet']['item']['systemStatus']['status']
print_status("instance #{instance_id} status: #{status}")
break if status == 'ok' || status != 'initializing'
end
instance_id
end
def create_keypair(creds)
action = 'ImportKeyPair'
doc = call_ec2(creds, 'Action' => action, 'KeyName' => datastore['KEY_NAME'], 'PublicKeyMaterial' => Rex::Text.encode_base64(datastore['SSH_PUB_KEY']))
if doc['Response'].nil?
doc = print_results(doc, action)
if doc['keyName'].nil? || doc['keyFingerprint'].nil?
print_error("Error creating key using privided key material (SSH_PUB_KEY)")
else
print_status("Created #{doc['keyName']} (#{doc['keyFingerprint']})")
end
else
if doc['Response']['Errors'] && doc['Response']['Errors']['Error']
print_error(doc['Response']['Errors']['Error']['Message'])
else
print_error("Error creating key using privided key material (SSH_PUB_KEY)")
end
end
end
def pub_subnet(creds, vpc_id)
# First look for subnets that are configured to provision a public IP when instances are launched
action = 'DescribeSubnets'
doc = call_ec2(creds, 'Action' => action)
doc = print_results(doc, action)
vpc_subnets = doc['subnetSet']['item'].select { |x| x['vpcId'] == vpc_id }
pub_subnets = vpc_subnets.select { |x| x['mapPublicIpOnLaunch'] == 'true' }
return pub_subnets.first['subnetId'] if pub_subnets.count > 0
# Second, try to retrieve public subnet id by looking through route tables to find subnets
# associated with an Internet gateway
action = 'DescribeRouteTables'
doc = call_ec2(creds, 'Action' => action)
doc = print_results(doc, action)
vpc_route_table = doc['routeTableSet']['item'].select { |x| x['vpcId'] == vpc_id }
vpc_route_table.each do |route_table|
next if route_table['associationSet'].nil? || route_table['routeSet'].nil?
entries = route_table['routeSet']['item']
if entries.instance_of?(Hash)
if entries['gatewayId'].start_with?('igw-')
return route_table['associationSet']['item'].first['subnetId']
end
else
route_table['routeSet']['item'].each do |route|
if route['gatewayId'] && route['gatewayId'].start_with?('igw-')
return route_table['associationSet']['item'].first['subnetId']
end
end
end
end
nil
end
def create_sg(creds, vpc_id)
name = Rex::Text.rand_text_alphanumeric(8)
action = 'CreateSecurityGroup'
doc = call_ec2(creds, 'Action' => action, 'GroupName' => name, 'VpcId' => vpc_id, 'GroupDescription' => name)
doc = print_results(doc, action)
print_error("Could not create SG") && return if doc['groupId'].nil?
sg = doc['groupId']
proto, port = datastore['SEC_GROUP_PORT'].split(':')
cidr = URI.encode(datastore['SEC_GROUP_CIDR'])
action = 'AuthorizeSecurityGroupIngress'
doc = call_ec2(creds, 'Action' => action,
'IpPermissions.1.IpRanges.1.CidrIp' => cidr,
'IpPermissions.1.IpProtocol' => proto,
'IpPermissions.1.FromPort' => port,
'IpPermissions.1.ToPort' => port,
'GroupId' => sg)
doc = print_results(doc, action)
if doc['return'] && doc['return'] == 'true'
print_status("Created security group: #{sg}")
else
print_error("Failed creating security group")
end
sg
end
def vpc(creds)
action = 'DescribeVpcs'
doc = call_ec2(creds, 'Action' => action)
doc = print_results(doc, action)
if doc['vpcSet'].nil? || doc['vpcSet']['item'].nil?
print_error("Could not determine VPC ID for #{datastore['AccessKeyId']} in #{datastore['RHOST']}")
return nil
end
item = doc['vpcSet']['item']
return item['vpcId'] if item.instance_of?(Hash)
return item.first['vpcId'] if item.instance_of?(Array) && !item.first['vpcId'].nil?
print_error("Could not determine VPC ID for #{datastore['AccessKeyId']} in #{datastore['RHOST']}")
nil
end
end