Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/aws/aws_launch_instances.rb
19758 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'metasploit/framework/aws/client'
7
8
class MetasploitModule < Msf::Auxiliary
9
include Metasploit::Framework::Aws::Client
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Launches Hosts in AWS',
16
'Description' => %q{
17
This module will attempt to launch an AWS instances (hosts) in EC2.
18
},
19
'License' => MSF_LICENSE,
20
'Author' => [
21
'Javier Godinez <godinezj[at]gmail.com>',
22
],
23
'References' => [
24
['URL', 'https://drive.google.com/open?id=0B2Ka7F_6TetSNFdfbkI1cnJHUTQ'],
25
['URL', 'https://published-prd.lanyonevents.com/published/rsaus17/sessionsFiles/4721/IDY-W10-DevSecOps-on-the-Offense-Automating-Amazon-Web-Services-Account-Takeover.pdf']
26
],
27
'Notes' => {
28
'Stability' => [CRASH_SAFE],
29
'SideEffects' => [IOC_IN_LOGS],
30
'Reliability' => []
31
}
32
)
33
)
34
register_options(
35
[
36
OptString.new('AccessKeyId', [true, 'AWS access key', '']),
37
OptString.new('SecretAccessKey', [true, 'AWS secret key', '']),
38
OptString.new('Token', [false, 'AWS session token', '']),
39
OptString.new('RHOST', [true, 'AWS region specific EC2 endpoint', 'ec2.us-west-2.amazonaws.com']),
40
OptString.new('Region', [true, 'The default region', 'us-west-2' ]),
41
OptString.new('AMI_ID', [true, 'The Amazon Machine Image (AMI) ID', 'ami-1e299d7e']),
42
OptString.new('KEY_NAME', [true, 'The SSH key to be used for ec2-user', 'admin']),
43
OptString.new('SSH_PUB_KEY', [false, 'The public SSH key to be used for ec2-user, e.g., "ssh-rsa ABCDE..."', '']),
44
OptString.new('USERDATA_FILE', [false, 'The script that will be executed on start', 'tools/modules/aws-aggregator-userdata.sh'])
45
]
46
)
47
register_advanced_options(
48
[
49
OptPort.new('RPORT', [true, 'AWS EC2 Endpoint TCP Port', 443]),
50
OptBool.new('SSL', [true, 'AWS EC2 Endpoint SSL', true]),
51
OptString.new('INSTANCE_TYPE', [true, 'The instance type', 'm3.medium']),
52
OptString.new('ROLE_NAME', [false, 'The instance profile/role name', '']),
53
OptString.new('VPC_ID', [false, 'The EC2 VPC ID', '']),
54
OptString.new('SUBNET_ID', [false, 'The public subnet to use', '']),
55
OptString.new('SEC_GROUP_ID', [false, 'The EC2 security group to use', '']),
56
OptString.new('SEC_GROUP_CIDR', [true, 'EC2 security group network access CIDR', '0.0.0.0/0']),
57
OptString.new('SEC_GROUP_PORT', [true, 'EC2 security group network access PORT', 'tcp:22']),
58
OptString.new('SEC_GROUP_NAME', [false, 'Optional EC2 security group name', '']),
59
OptInt.new('MaxCount', [true, 'Maximum number of instances to launch', 1]),
60
OptInt.new('MinCount', [true, 'Minumum number of instances to launch', 1])
61
]
62
)
63
deregister_options('VHOST')
64
end
65
66
def run
67
if datastore['AccessKeyId'].blank? || datastore['SecretAccessKey'].blank?
68
print_error('Both AccessKeyId and SecretAccessKey are required')
69
return
70
end
71
# setup creds for making IAM API calls
72
creds = {
73
'AccessKeyId' => datastore['AccessKeyId'],
74
'SecretAccessKey' => datastore['SecretAccessKey']
75
}
76
creds['Token'] = datastore['Token'] unless datastore['Token'].blank?
77
78
create_keypair(creds) unless datastore['SSH_PUB_KEY'].blank?
79
vpc = datastore['VPC_ID'].blank? ? vpc(creds) : datastore['VPC_ID']
80
sg = datastore['SEC_GROUP_ID'].blank? ? create_sg(creds, vpc) : datastore['SEC_GROUP_ID']
81
subnet = datastore['SUBNET_ID'].blank? ? pub_subnet(creds, vpc) : datastore['SUBNET_ID']
82
unless subnet
83
print_error('Could not find a public subnet, please provide one')
84
return
85
end
86
instance_id = launch_instance(creds, subnet, sg)
87
action = 'DescribeInstances'
88
doc = call_ec2(creds, 'Action' => action, 'InstanceId.1' => instance_id)
89
doc = print_results(doc, action)
90
begin
91
# need a better parser so we can avoid shit like this
92
ip = doc['reservationSet']['item']['instancesSet']['item']['networkInterfaceSet']['item']['privateIpAddressesSet']['item']['association']['publicIp']
93
print_status("Instance #{instance_id} has IP address #{ip}")
94
rescue NoMethodError
95
print_error('Could not retrieve instance IP address')
96
end
97
end
98
99
def opts(action, subnet, sg)
100
opts = {
101
'Action' => action,
102
'ImageId' => datastore['AMI_ID'],
103
'KeyName' => datastore['KEY_NAME'],
104
'InstanceType' => datastore['INSTANCE_TYPE'],
105
'NetworkInterface.1.SubnetId' => subnet,
106
'NetworkInterface.1.SecurityGroupId.1' => sg,
107
'MinCount' => datastore['MinCount'].to_s,
108
'MaxCount' => datastore['MaxCount'].to_s,
109
'NetworkInterface.1.AssociatePublicIpAddress' => 'true',
110
'NetworkInterface.1.DeviceIndex' => '0'
111
}
112
opts['IamInstanceProfile.Name'] = datastore['ROLE_NAME'] unless datastore['ROLE_NAME'].blank?
113
114
fname = datastore['USERDATA_FILE']
115
unless fname.blank?
116
if File.exist?(fname)
117
opts['UserData'] = URI::DEFAULT_PARSER.escape(Base64.encode64(::File.read(fname, mode: 'r').strip))
118
else
119
print_error("Could not open userdata file: #{fname}")
120
end
121
end
122
opts
123
end
124
125
def launch_instance(creds, subnet, sg)
126
action = 'RunInstances'
127
print_status("Launching instance(s) in #{datastore['Region']}, AMI: #{datastore['AMI_ID']}, key pair name: #{datastore['KEY_NAME']}, security group: #{sg}, subnet ID: #{subnet}")
128
doc = call_ec2(creds, opts(action, subnet, sg))
129
doc = print_results(doc, action)
130
return if doc.nil?
131
132
# TODO: account for multiple instances
133
if doc['instancesSet']['item'].instance_of?(Array)
134
instance_id = doc['instancesSet']['item'].first['instanceId']
135
else
136
instance_id = doc['instancesSet']['item']['instanceId']
137
end
138
print_status("Launched instance #{instance_id} in #{datastore['Region']} account #{doc['ownerId']}")
139
action = 'DescribeInstanceStatus'
140
loop do
141
sleep(15)
142
doc = call_ec2(creds, 'Action' => action, 'InstanceId' => instance_id)
143
doc = print_results(doc, action)
144
if doc['instanceStatusSet'].nil?
145
print_error('Error, could not get instance status, instance possibly terminated')
146
break
147
end
148
status = doc['instanceStatusSet']['item']['systemStatus']['status']
149
print_status("instance #{instance_id} status: #{status}")
150
break if status == 'ok' || status != 'initializing'
151
end
152
instance_id
153
end
154
155
def create_keypair(creds)
156
action = 'ImportKeyPair'
157
doc = call_ec2(creds, 'Action' => action, 'KeyName' => datastore['KEY_NAME'], 'PublicKeyMaterial' => Rex::Text.encode_base64(datastore['SSH_PUB_KEY']))
158
if doc['Response'].nil?
159
doc = print_results(doc, action)
160
if doc['keyName'].nil? || doc['keyFingerprint'].nil?
161
print_error('Error creating key using provided key material (SSH_PUB_KEY)')
162
else
163
print_status("Created #{doc['keyName']} (#{doc['keyFingerprint']})")
164
end
165
elsif doc['Response']['Errors'] && doc['Response']['Errors']['Error']
166
print_error(doc['Response']['Errors']['Error']['Message'])
167
else
168
print_error('Error creating key using provided key material (SSH_PUB_KEY)')
169
end
170
end
171
172
def pub_subnet(creds, vpc_id)
173
# First look for subnets that are configured to provision a public IP when instances are launched
174
action = 'DescribeSubnets'
175
doc = call_ec2(creds, 'Action' => action)
176
doc = print_results(doc, action)
177
vpc_subnets = doc['subnetSet']['item'].select { |x| x['vpcId'] == vpc_id }
178
pub_subnets = vpc_subnets.select { |x| x['mapPublicIpOnLaunch'] == 'true' }
179
return pub_subnets.first['subnetId'] if pub_subnets.count > 0
180
181
# Second, try to retrieve public subnet id by looking through route tables to find subnets
182
# associated with an Internet gateway
183
action = 'DescribeRouteTables'
184
doc = call_ec2(creds, 'Action' => action)
185
doc = print_results(doc, action)
186
vpc_route_table = doc['routeTableSet']['item'].select { |x| x['vpcId'] == vpc_id }
187
vpc_route_table.each do |route_table|
188
next if route_table['associationSet'].nil? || route_table['routeSet'].nil?
189
190
entries = route_table['routeSet']['item']
191
if entries.instance_of?(Hash)
192
if entries['gatewayId'].start_with?('igw-')
193
return route_table['associationSet']['item'].first['subnetId']
194
end
195
else
196
route_table['routeSet']['item'].each do |route|
197
if route['gatewayId'] && route['gatewayId'].start_with?('igw-')
198
return route_table['associationSet']['item'].first['subnetId']
199
end
200
end
201
end
202
end
203
nil
204
end
205
206
def create_sg(creds, vpc_id)
207
name = Rex::Text.rand_text_alphanumeric(8)
208
action = 'CreateSecurityGroup'
209
doc = call_ec2(creds, 'Action' => action, 'GroupName' => name, 'VpcId' => vpc_id, 'GroupDescription' => name)
210
doc = print_results(doc, action)
211
print_error('Could not create SG') && return if doc['groupId'].nil?
212
213
sg = doc['groupId']
214
proto, port = datastore['SEC_GROUP_PORT'].split(':')
215
cidr = URI::DEFAULT_PARSER.escape(datastore['SEC_GROUP_CIDR'])
216
action = 'AuthorizeSecurityGroupIngress'
217
doc = call_ec2(creds, 'Action' => action,
218
'IpPermissions.1.IpRanges.1.CidrIp' => cidr,
219
'IpPermissions.1.IpProtocol' => proto,
220
'IpPermissions.1.FromPort' => port,
221
'IpPermissions.1.ToPort' => port,
222
'GroupId' => sg)
223
doc = print_results(doc, action)
224
if doc['return'] && doc['return'] == 'true'
225
print_status("Created security group: #{sg}")
226
else
227
print_error('Failed creating security group')
228
end
229
sg
230
end
231
232
def vpc(creds)
233
action = 'DescribeVpcs'
234
doc = call_ec2(creds, 'Action' => action)
235
doc = print_results(doc, action)
236
if doc['vpcSet'].nil? || doc['vpcSet']['item'].nil?
237
print_error("Could not determine VPC ID for #{datastore['AccessKeyId']} in #{datastore['RHOST']}")
238
return nil
239
end
240
item = doc['vpcSet']['item']
241
return item['vpcId'] if item.instance_of?(Hash)
242
return item.first['vpcId'] if item.instance_of?(Array) && !item.first['vpcId'].nil?
243
244
print_error("Could not determine VPC ID for #{datastore['AccessKeyId']} in #{datastore['RHOST']}")
245
nil
246
end
247
end
248
249