-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.py
281 lines (249 loc) · 9.47 KB
/
index.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import json, boto3, time, datetime, logging
def lambda_handler(event, context):
# Set to 1 to revoke current role sessions, this will disrupt other instances using
# the role until they are restarted or request new temporary credentials.
REVOKE_CURRENT_SESSIONS = 1
ec2_client = boto3.client('ec2')
ssm_client = boto3.client('ssm')
iam_client = boto3.client('iam')
s3_client = boto3.client('s3')
#log_level = "DEBUG"
log_level = "INFO"
logging.getLogger(__name__).setLevel(log_level)
logger = logging.getLogger(__name__)
logger.debug(event)
# Get Instance Id From Event
# Note: this only checks for Inspector, additional ProductNames should be added
logger.info("Get Instance ID")
if event['detail']['findings'][0]['ProductName'] == 'Inspector':
instance_id = event['detail']['findings'][0]['Resources'][0]['Id'].split("/")[1]
logger.info(instance_id)
else:
logger.error("Instance Id Not Found.")
return 0
# Check if instance is already quarantined
response = ec2_client.describe_tags(
Filters=[
{
'Name': 'resource-id',
'Values': [instance_id]
},
{
'Name': 'key',
'Values': ['Quarantine']
}
]
)
tags = response['Tags']
if tags:
quarantine_value = tags[0]['Value']
if quarantine_value == 'true':
logger.error("ERROR: Quarantine tag true. Instance already quarantined.")
return 999
# Get EC2 VPC Id
vpc_id = ec2_client.describe_instances(InstanceIds=[instance_id])['Reservations'][0]['Instances'][0]['VpcId']
logger.debug(vpc_id)
# Get Instance Role
logger.info("Get Instance Role")
instance_profile = ec2_client.describe_instances(InstanceIds=[instance_id])['Reservations'][0]['Instances'][0]['IamInstanceProfile']['Arn'].split("/")[1]
logger.debug(instance_profile)
instance_role = iam_client.get_instance_profile(InstanceProfileName=instance_profile)['InstanceProfile']['Roles'][0]['RoleName']
logger.info(instance_role)
# Check to make sure Quarantine Role is not already attached to Instance
if "QuarantineRole" in instance_role:
logger.info("Error: Quarantine Role Already Attached to Instance")
return 999
# Get Instance Instance Profile Association Id
association_id = ec2_client.describe_iam_instance_profile_associations(
Filters=[{'Name': 'instance-id', 'Values': [instance_id]}]
)['IamInstanceProfileAssociations'][0]['AssociationId']
logger.debug(association_id)
# Get Parameters
quarantine_sg = ssm_client.get_parameter(Name='/resources/securitygroups/' + vpc_id + '/QuarantineSecurityGroupVPC1/GroupId')['Parameter']['Value']
quarantine_instance_profile = ssm_client.get_parameter(Name='/resources/iam/instance-profiles/QuarantineRoleInstanceProfile/Arn')['Parameter']['Value']
forensic_scripts_bucket = ssm_client.get_parameter(Name='/resources/s3/ForensicsScriptsBucket/Name')['Parameter']['Value']
forensic_data_bucket = ssm_client.get_parameter(Name='/resources/s3/ForensicsDataBucket/Name')['Parameter']['Value']
# Tag instance
logger.info("Tag Instance")
response = ec2_client.create_tags(
Resources=[instance_id],
Tags=[
{
'Key': 'Quarantine',
'Value': 'true'
},
]
)
# Detach from ASG (if applicable)
# Warning this will launch a new instance with the same vulnerability
# Not applicable in this demo
# Attach quarantine security group
logger.info("Add Quarantine Security Group to Instance")
response = ec2_client.modify_instance_attribute(
InstanceId=instance_id,
Groups=[quarantine_sg]
)
logger.debug(response)
# Attach quarantine role
# Note: This will not work if the instance is stopped already
logger.info("Replace Instance Profile with Quarantine Role")
response = ec2_client.replace_iam_instance_profile_association(
IamInstanceProfile={
'Arn': quarantine_instance_profile
},
AssociationId=association_id
)
logger.debug(response)
# Policy to revoke old sessions
iso8601Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
logger.info(iso8601Time)
deny_older_sessions_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": ["*"],
"Resource": ["*"],
"Condition": {
"DateLessThan": {
"aws:TokenIssueTime": iso8601Time
}
}
}
]
}
# Invalidate sessions of original role
# Other instances currently using this role may need to be stopped and restarted to receive fresh credentials
if REVOKE_CURRENT_SESSIONS:
logger.info("Revoking Old Role Sessions")
response = iam_client.put_role_policy(
RoleName=instance_role,
PolicyName='RevokeOlderSessions',
PolicyDocument=str(json.dumps(deny_older_sessions_policy))
)
logger.debug(response)
# Pause for Instance Profile to be Attached
logger.info("Pause for 1 minute for Instance Profile to Attach")
time.sleep(60)
# Download and run forensic script
logger.info("Collect Forensic Data from Instance")
response = ssm_client.send_command(
InstanceIds=[instance_id],
DocumentName="AWS-RunShellScript",
Parameters={"commands": [
"mkdir /root/forensics || true",
"cd /root/forensics",
"aws s3 cp s3://" + forensic_scripts_bucket + "/get-forensic-data.sh ./",
"chmod 700 get-forensic-data.sh",
"./get-forensic-data.sh"
]}
)
logger.debug(response)
command_id = response['Command']['CommandId']
logger.debug(command_id)
sleep = 30
timeout = 600
time.sleep(sleep)
elapsed_time = sleep
status = "InProgress"
# Wait for run command to finish
while elapsed_time < timeout and status == "InProgress":
logger.info("InProgress and Timeout Not Reached.")
output = ssm_client.get_command_invocation(
CommandId=command_id,
InstanceId=instance_id,
)
status = output['Status']
elapsed_time += sleep
time.sleep(sleep)
logger.debug(output)
if elapsed_time >= timeout:
logger.error("Fatal Error: Run Command Timed Out.")
return 999
else:
logger.info("Run Command Finished.")
# Get objects to enable legalhold object lock in s3
logger.info("Enable legal hold for forensic data uploaded to S3")
response = s3_client.list_objects_v2(
Bucket=forensic_data_bucket,
Prefix=instance_id
)
keys = response['Contents']
# Check the data was uploaded
if len(keys) == 0:
logger.error("Fatal Error: No forensic data files found in S3.")
return 999
# Enable legal hold
for key in keys:
file_name = key['Key']
logger.debug(file_name)
response = s3_client.put_object_legal_hold(
Bucket=forensic_data_bucket,
Key=file_name,
LegalHold={'Status': 'ON'}
)
logger.debug(response)
# Power down instance
logger.info("Powering Down Instance")
response = ec2_client.stop_instances(InstanceIds=[instance_id])
# Wait for power off
time.sleep(60)
response = ec2_client.describe_instances(InstanceIds=[instance_id])
# check if instance is stopped, if not force shutdown
instance_state = response['Reservations'][0]['Instances'][0]['State']['Name']
if instance_state != "stopped":
logger.info("Force Stop Instance")
response = ec2_client.stop_instances(InstanceIds=[instance_id], Force=True)
time.sleep(60)
# Create an ami (snapshot)
logger.info("Create an AMI of Instance")
# Global Tags (for AMI and Snapshot)
ami_global_tags = [
{
'Key': 'InstanceId',
'Value': instance_id
},
{
'Key': 'Timestamp',
'Value': iso8601Time
},
{
'Key': 'Quarantine',
'Value': 'true'
},
{
'Key': 'Type',
'Value': 'ForensicImage'
}]
# AMI Tags
ami_tags = [{
'Key': 'Name',
'Value': 'FORENSIC-IMAGE - Compromised EC2 - ' + instance_id
}]
ami_tags.extend(ami_global_tags)
# Snapshot Tags
snapshot_tags = [{
'Key': 'Name',
'Value': 'FORENSIC-SNAPSHOT - Compromised EC2 - ' + instance_id
}]
snapshot_tags.extend(ami_global_tags)
logger.info("Create AMI")
ami_time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).strftime("%Y-%m-%d-%H-%M-%S%Z")
response = ec2_client.create_image(
Description='This AMI was created by Lambda, via SecurityHub, and contains the image of an instance suspected of being compromised. ' + instance_id + ' ' + iso8601Time,
InstanceId=instance_id,
Name='FORENSIC-IMAGE - ' + instance_id + ' - ' + ami_time,
TagSpecifications=[
{
'ResourceType': 'image',
'Tags': ami_tags
},
{
'ResourceType': 'snapshot',
'Tags': snapshot_tags
}
]
)
logger.info("Script Completed: Forensinc data collected.")
logger.info("Allow extra time for the AMI to complete.")
return 1