-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresponses.py
More file actions
427 lines (347 loc) · 22.1 KB
/
responses.py
File metadata and controls
427 lines (347 loc) · 22.1 KB
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
'''
For Alert: This example includes the extraction of Radware's CWP json Alert from SNS,
extraction of relevant AWS resources that could have been compromised
and the responses available in order to stop any malicious operations by those resources.
For compromised users we can respond by blocking his console login (delete login profile),
by blocking his cli key (deactivate access key) or 'suspend' his activity by
setting deny permission boundary to make all methods unavailable for the user.
For compromised roles and instances we can respond by disassociating the role
from instance (to prevent future attempts to use the machine for malicious actions
with the permissions of the role) and revoke the role with deny permission to
interrupt any ongoing assuming of the role, or quarantine the instance by attaching it
a security group which will prevent any inbound and outbound traffic and reboot
the instance to interrupt any current connection to it, or simply stop the instance.
For compromised databases we can respond by preventing all access to the database
by attaching it a security group which will prevent any inbound and outbound traffic
and reboot the database to interrupt any current connection to it.
For cloudtrail logs, if a trail was stopped, we can re-start it.
For S3 buckets, if a bucket was made publicly accessed, we can block it.
For Hardening: This example includes the extraction of Radware's CWP json Hardening Warning (Misconfiguration, Exposed Machines, Exposed Database) from SNS,
extraction of relevant AWS resources that failed to pass misconfiguration rules or that are exposed
and the responses available in order to avoid option of attackers to take advantage of these vulnerabilities.
For users with console login and no MFA configured we can respond by blocking his console login (delete login profile).
For users with unused credentials we can respond by blocking his cli key (deactivate access key).
For exposed machines and databases we can remove the inbound rule that is the reason for the exposure.
'''
import datetime
def handle_alert(boto_session, alert):
# handle alert
# create aws client for ec2 service
ec2_client = boto_session.client('ec2')
# create aws client for iam service
iam_client = boto_session.client('iam')
# create aws client for rds service
rds_client = boto_session.client('rds')
# create aws client for cloudtrail service
cloudtrail_client = boto_session.client('cloudtrail')
# create aws client for s3 service
s3_client = boto_session.client('s3')
# involved identities are users and roles which were somehow part of the alert -
# extract only users (roles are also in involvedIdentities)
users = [involvedIdentity for involvedIdentity in alert['involvedIdentities'] if 'user' in involvedIdentity['id']]
'''you can delete user login profile to block console login
or/and deactivate access key to block cli usage
or set permission boundary to make all methods unavailable for the user'''
user_names = [user['name'] for user in users]
delete_users_login_profile(iam_client, user_names)
deactivate_users_access_keys(iam_client, user_names)
set_deny_all_permission_boundary(iam_client, users)
# involved resources are machines which were somehow part of the alert and check that we have the instanceId
machines = [involvedResource for involvedResource in alert['involvedResource'] if 'id' in involvedResource]
'''you can disassociate role from machine
or/and quarantine instance by security group
or/and simply stop the machine'''
disassociate_role_from_machines(ec2_client, iam_client, machines)
quarantine_instances_by_security_group(ec2_client, machines)
stop_instances(ec2_client, machines)
# find all rds involved - put in set to avoid duplications
rds_dbs = {activity['affectedResources']['rds'] for activity in alert['activities'] if 'rds' in activity['affectedResources']}
# quarantine rds by security group (DB security group and VPC security group) and reboot to stop
# current open connections
quarantine_rds_by_security_group(rds_client, ec2_client, rds_dbs)
# get tail ARN of the activities with name like "cloudtrailStopLogs", if so - start CloudTrail logging
cloudtrial_arns = [activity['affectedResources']['trailName'] for activity in alert['activities'] if activity['name'] == 'cloudtrailStopLogs' and 'trailName' in activity['affectedResources']]
start_cloudtrail_logging(cloudtrail_client, cloudtrial_arns)
# get bucket names of the activities with name like "publicS3Bucket",
# if bucket made publicly accessible - block its public access
buckets = [activity['affectedResources']['bucketName'] for activity in alert['activities'] if activity['name'] == 'publicS3Bucket' and 'bucketName' in activity['affectedResources']]
block_public_access(s3_client, buckets)
def handle_hardening(boto_session, hardening):
# handle hardening
# create aws client for iam service
iam_client = boto_session.client('iam')
# create aws client for ec2 service
ec2_client = boto_session.client('ec2')
# create aws client for rds service
rds_client = boto_session.client('rds')
if hardening['hardeningType'] == 'Misconfiguration':
handle_misconfiguration(iam_client, hardening)
elif hardening['hardeningType'] == 'ExposedMachines':
handle_exposed_machines(ec2_client, hardening)
elif hardening['hardeningType'] == 'ExposedDatabase':
handle_exposed_database(ec2_client, rds_client, hardening)
elif hardening['hardeningType'] == 'PublicS3Bucket':
handle_public_bucket(hardening)
def handle_misconfiguration(iam_client, hardening):
# handle misconfiguration
# extract misconfiguration rule id
rule_id = hardening['ruleId']
if rule_id == 'RDWR.AWS.IAM.UserMFAEnabled':
# make sure resource type is IamUser
assert hardening['resourceType'] == 'User'
# this will extract all usernames of users which 'failed' some misconfiguration rule on IamUser
users = [user['name'] for user in hardening['failedResources']]
# you can delete user login profile to block console login
delete_users_login_profile(iam_client, [user for user in users if check_user_created_at_least_day_ago(iam_client, user)])
elif rule_id == 'RDWR.AWS.IAM.UnusedCredentials1ShouldBeDisabled':
# make sure resource type is IamUser
assert hardening['resourceType'] == 'User'
# this will extract all usernames of users which 'failed' some misconfiguration rule on IamUser
users =[user['name'] for user in hardening['failedResources']]
# you can deactivate access key to block cli usage
deactivate_users_access_keys(iam_client, users)
def handle_public_bucket(hardening):
# handle public buckets
return
def handle_exposed_machines(ec2_client, hardening):
# handle exposed machines
# if SSh(port 22), RDP(port 3389) or all ports are open, remove the exposed port from
# security group inbound ip permissions
if '22' in hardening['openPorts'] or '3389' in hardening['openPorts'] or '0-65535' in hardening['openPorts']:
remove_exposed_port_from_inbound(ec2_client, hardening['securityGroup'], [22, 3389])
def handle_exposed_database(ec2_client, rds_client, hardening):
# handle exposed databases
# check if type of security group is VPC(EC2) or DB
if hardening['securityGroup']['type'] == 'EC2':
remove_exposed_port_from_inbound(ec2_client, hardening['securityGroup'], hardening['openPorts'])
elif hardening['securityGroup']['type'] == 'DB':
remove_cidrip_from_db_sg(rds_client, hardening['securityGroup'])
def remove_cidrip_from_db_sg(rds_client, security_group):
# remove exposed inbound CIDR/IP from DB security group
try:
# remove open inbound CIDR/IP from DB security group
rds_client.revoke_db_security_group_ingress(DBSecurityGroupName=security_group['name'],CIDRIP='0.0.0.0/0')
print("modified DB security group: " + str(security_group))
except Exception as e:
print("could not modify DB security group " + str(security_group) + ". message: " + str(e))
def remove_exposed_port_from_inbound(ec2_client, security_group, ports):
# remove exposed inbound ip permissions from ec2 (vpc) security group
try:
# describe security group in order to get its inbound ip permissions
groups = ec2_client.describe_security_groups(GroupIds=[security_group['id']])
for group in groups['SecurityGroups']:
try:
# remove all ip permissions which are open (all ports, SSH(22), RDP(3389))
ip_permissions = [ip_permission for ip_permission in group['IpPermissions']
if (any(ip_range['CidrIp'] == '0.0.0.0/0' for ip_range in ip_permission['IpRanges']) or any(ipv6_range['CidrIpv6'] == '::/0' for ipv6_range in ip_permission['Ipv6Ranges']))
and ((ip_permission['IpProtocol'] == '-1' or (ip_permission['FromPort'] == 0 and ip_permission['ToPort'] == 65535))
or any((ip_permission['FromPort'] <= port <= ip_permission['ToPort']) for port in ports))]
# remove open inbound ip permissions from security group
ec2_client.revoke_security_group_ingress(GroupId=group['GroupId'],IpPermissions=ip_permissions)
print("modified security group: " + str(group))
except Exception as e:
print("could not modify security group " + str(group) + ". message: " + str(e))
except Exception as e:
print("could not describe security group " + str(security_group) + ". message: " + str(e))
def block_public_access(s3_client, buckets):
# block public access of buckets
for bucket in buckets:
try:
s3_client.put_public_access_block(Bucket=bucket,PublicAccessBlockConfiguration={'BlockPublicPolicy': True})
print("blocked public access for " + str(bucket))
except Exception as e:
print("could not block public access for " + str(bucket) + ". message: " + str(e))
def start_cloudtrail_logging(cloudtrail_client, cloudtrial_arns):
# start cloudtrail logging
for cloudtrial_arn in cloudtrial_arns:
try:
cloudtrail_client.start_logging(Name=cloudtrial_arn)
print("started cloudtrail logging for " + str(cloudtrial_arn))
except Exception as e:
print("could not start cloudtrail logging " + str(cloudtrial_arn) + ". message: " + str(e))
def stop_instances(ec2_client, machines):
# simply stop instance
for machine in machines:
try:
instance_id = machine['id']
# in order to close current connection we must stop/reboot the instance by instance id
ec2_client.stop_instances(InstanceIds=[instance_id],Force=True)
print("stopped instance " + str(instance_id))
except Exception as e:
print("could not stop instance " + str(machine) + ". message: " + str(e))
def get_empty_security_group(ec2_client, vpc_id):
# find or create if not exists new empty security group
group_name = 'RadwareCwpEmptySecurityGroup_' + vpc_id
try:
security_group_list = ec2_client.describe_security_groups(GroupNames=[group_name])
security_group = security_group_list['SecurityGroups'][0]
print("found security group: " + str(security_group))
except:
group_id_map = ec2_client.create_security_group(Description='security group with no inbound/outbound permission for CWP', GroupName=group_name, VpcId=vpc_id)
# describe again
security_group_list = ec2_client.describe_security_groups(GroupIds=[group_id_map['GroupId']])
security_group = security_group_list['SecurityGroups'][0]
print("created security group: " + str(security_group))
# make sure that there are no inbound and outbound permissions
if len(security_group['IpPermissionsEgress']) > 0:
ec2_client.revoke_security_group_egress(GroupId=security_group['GroupId'],IpPermissions=security_group['IpPermissionsEgress'])
if len(security_group['IpPermissions']) > 0:
ec2_client.revoke_security_group_ingress(GroupId=security_group['GroupId'],IpPermissions=security_group['IpPermissions'])
return security_group['GroupId']
def quarantine_rds_by_security_group(rds_client, ec2_client, rds_dbs):
# quarantine rds by associating it to security group which has no inbound/outbound permissions
for rds in rds_dbs:
modified = False
# modify rds instance security group and vpc security group to security group that has no inbound/outbound
# permission by db instance and by security group id
try:
# describe rds in order to get its vpc
rds_list = rds_client.describe_db_instances(DBInstanceIdentifier=rds)
rds_details = rds_list['DBInstances'][0]
security_group_id = get_empty_security_group(ec2_client, rds_details['DBSubnetGroup']['VpcId'])
rds_client.modify_db_instance(DBInstanceIdentifier=rds,DBSecurityGroups=[security_group_id],ApplyImmediately=True)
print("modified rds instance " + str(rds) + " security group to " + str(security_group_id))
modified = True
except Exception as e:
print("could not modify rds instance security group " + str(rds) + ". message: " + str(e))
try:
rds_client.modify_db_instance(DBInstanceIdentifier=rds,VpcSecurityGroupIds=[security_group_id])
print("modified rds instance " + str(rds) + " vpc security group to " + str(security_group_id))
modified = True
except Exception as e:
print("could not modify rds instance vpc security group " + str(rds) + ". message: " + str(e))
if modified:
try:
# in order to close current connection we must also reboot the rds instance by db instance identifier
rds_client.reboot_db_instance(DBInstanceIdentifier=rds)
print("rebooted rds instance " + str(rds) + " to interrupt current connections")
except Exception as e:
print("could not reboot rds " + str(rds) + ". message: " + str(e))
def quarantine_instances_by_security_group(ec2_client, machines):
# quarantine instance by associating it to security group which has no inbound/outbound permissions
for machine in machines:
try:
instance_id = machine['id']
vpc_id = machine['vpcId']
security_group_id = get_empty_security_group(ec2_client, vpc_id)
# modify instance security group to security group that has no inbound/outbound permission by instance id
# and by security group id
ec2_client.modify_instance_attribute(InstanceId=instance_id,Groups=[security_group_id])
print("modify instance " + str(instance_id) + " security group to " + str(security_group_id))
try:
# in order to close current connection we must also reboot the instance by instance id
ec2_client.reboot_instances(InstanceIds=[instance_id])
print("rebooted instance " + str(instance_id) + " to interrupt current connections")
except Exception as e:
print("could not reboot machine " + str(machine) + ". message: " + str(e))
except Exception as e:
print("could not modify security group for machine " + str(machine) + ". message: " + str(e))
def disassociate_role_from_machines(ec2_client, iam_client, machines):
# describe instance association profiles and disassociate the from instance
# inline policy to revoke all role sessions
role_revoke_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Action\":[\"*\"]," \
"\"Resource\":[\"*\"],\"Condition\":{\"DateLessThan\":{\"aws:TokenIssueTime\":\"%s\"}}}]} "
for machine in machines:
try:
instance_id = machine['id']
# get all instance profile associations by instance id
response = ec2_client.describe_iam_instance_profile_associations(Filters=[{'Name': 'instance-id','Values': [instance_id]},{'Name': 'state','Values': ['associating','associated']}])
iam_instance_profile_associations = response['IamInstanceProfileAssociations']
# iterate through all profiles and disassociate them from instance
for iam_instance_profile_association in iam_instance_profile_associations:
try:
# disassociate instance profile by association id
ec2_client.disassociate_iam_instance_profile(AssociationId=iam_instance_profile_association['AssociationId'])
print("disassociated iam instance profile " + str(iam_instance_profile_association))
try:
# extract role name from arn of profile association
role_name = iam_instance_profile_association['IamInstanceProfile']['Arn'].split('/')[-1]
# Current Time
time = datetime.datetime.utcnow().isoformat()
# the attacker might have assumed the role and can use it up to 12 hours unless we revoke the
# active sessions
iam_client.put_role_policy(RoleName=role_name,PolicyName='AWSRevokeOlderSessions',PolicyDocument=role_revoke_policy % time)
print("revoked role: " + str(role_name))
except Exception as e:
print("could not revoke role " + str(role_name) + ". message: " + str(e))
except Exception as e:
print("could not Disassociate iam instance profile for " + str(iam_instance_profile_association) + ". message: " + str(e))
except Exception as e:
print("could not Describe and Disassociate Role From Machine for " + str(machine) + ". message: " + str(e))
def get_deny_all_policy(iam_client, account_id):
# get deny all policy if exists or create it if it doesn't
deny_policy_name = 'CwpDenyAllPolicy'
deny_policy_arn = 'arn:aws:iam::' + account_id + ':policy/' + deny_policy_name
deny_policy_permissions = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Action\":[\"*\"]," \
"\"Resource\":[\"*\"]}]} "
try:
# get policy by policy arn
deny_policy = iam_client.get_policy(PolicyArn=deny_policy_arn)
print("found policy " + deny_policy_name)
except Exception as e:
# check if no policy with the above arn was found
if e.response['Error']['Code'] == 'NoSuchEntity':
# create policy with the above details
deny_policy = iam_client.create_policy(PolicyName=deny_policy_name, PolicyDocument=deny_policy_permissions, Description='Deny All Policy for CWP')
print("created policy " + deny_policy_name)
else:
print("error in get_deny_all_policy: " + str(e))
return None
return deny_policy['Policy']['Arn']
def set_deny_all_permission_boundary(iam_client, involved_users):
# set deny all permission boundary for all involved users
if len(involved_users) > 0:
# get the account id
account_id = involved_users[0]['id'].split(':')[4]
# get or create deny policy - return arn
deny_policy_arn = get_deny_all_policy(iam_client, account_id)
for involved_user in involved_users:
try:
user_name = involved_user['name']
iam_client.put_user_permissions_boundary(UserName=user_name, PermissionsBoundary=deny_policy_arn)
print("DenyAll Permission Boundary was set to " + str(involved_user))
except Exception as e:
print("could not set DenyAll Permission Boundary to " + str(involved_user) + ". message: " + str(e))
else:
print("involved users list is empty")
def deactivate_users_access_keys(iam_client, user_names):
# describe users access keys and deactivate them
for user_name in user_names:
try:
# get all access keys of the user
res = iam_client.list_access_keys(UserName=user_name)
# extract keys from metadata
keys = [key['AccessKeyId'] for key in res['AccessKeyMetadata']]
# deactivate all user's access keys
for key in keys:
try:
iam_client.update_access_key(AccessKeyId=key, UserName=user_name, Status='Inactive')
print("deactivated " + str(user_name) + " access key " + str(key))
except Exception as e:
print("could not deactivate " + str(user_name) + " access key " + str(key) + ". message: " + str(e))
except Exception as e:
print("could not deactivate " + str(user_name) + " access keys. message: " + str(e))
def delete_users_login_profile(iam_client, user_names):
# delete users login profile and block console login
for user_name in user_names:
try:
iam_client.delete_login_profile(UserName=user_name)
print("deleted/disabled " + str(user_name) + " login profile")
except Exception as e:
print("could not delete/disable " + str(user_name) + " login profile. message: " + str(e))
def check_user_created_at_least_day_ago(iam_client, user_name):
# check that the user was created at least a full day ago
try:
describe_user = iam_client.get_user(UserName=user_name)
# get yesterday's date
yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
# compare user create date (without time zone) with yesterday date
if describe_user['User']['CreateDate'].replace(tzinfo=None) <= yesterday:
print("user was created before the last 24H: " + str(describe_user))
return True
else:
print("user was created in the last 24H: " + str(describe_user))
return False
except Exception as e:
print("could not describe " + str(user_name) + ", continuing. message: " + str(e))
return False