Initial checkin of launch instances module
parent
06e6a973ce
commit
26d344a0ef
|
@ -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 ~]$
|
||||
```
|
|
@ -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
|
Loading…
Reference in New Issue