Skip to content

Commit 1269d48

Browse files
author
Nicolas Béguier
authored
Merge pull request #32 from leboncoin/4.4.3
4.4.3
2 parents 2a11619 + 41fbf58 commit 1269d48

File tree

8 files changed

+87
-17
lines changed

8 files changed

+87
-17
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ CHANGELOG
44
AWS-TOWER
55
-----
66

7+
4.4.3
8+
-----
9+
10+
2023/03/10
11+
12+
### New feature
13+
- Display EC2 OS info and improve filter
14+
- Pattern: add rule has_attribute_contain*
15+
- Add --only-dangerous-actions for iam verb
16+
17+
### Fixtures
18+
- Fix `--layer` and add Usage in README
19+
20+
721
4.4.2
822
-----
923

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ options:
116116
117117
```bash
118118
$ aws-tower iam --help
119-
usage: aws_tower_cli.py iam [-h] [-s SOURCE] [-a ACTION] [--min-rights {admin,poweruser,reader}] [--service SERVICE] [-d] [-v] profile
119+
usage: aws_tower_cli.py iam [-h] [-s SOURCE] [-a ACTION] [--min-rights {admin,poweruser,reader}] [--service SERVICE] [-d] [--only-dangerous-actions] [-v] profile
120120
121121
positional arguments:
122122
profile A valid profile name configured in the ~/.aws/config file
@@ -131,6 +131,8 @@ options:
131131
Minimum actions rights
132132
--service SERVICE Action Category to match
133133
-d, --display Display informations about the source ARN
134+
--only-dangerous-actions
135+
Display IAM dangerous actions only
134136
-v, --verbose Verbose output of the account assets
135137
```
136138
@@ -150,6 +152,22 @@ $ export PATROWL_PUBLIC_ENDPOINT=http://localhost/
150152
$ python -c 'from monitoring.aws_lambda import aws_tower_child; aws_tower_child.main({ "my-account-profile": "arn:aws:iam::xxxxxxxxxxxxx:role/readonly", "env": "pro|pre|dev", "region_name": "eu-west-1", "meta_types": ["S3"] })'
151153
```
152154
155+
156+
## Usage (layers)
157+
158+
```bash
159+
$ aws-tower --layer > /tmp/aws-tower-layer.json
160+
```
161+
162+
Then, go to [Attack Navigator](https://mitre-attack.github.io/attack-navigator/#comment_underline=false)
163+
164+
Click on "Open Existing Layer" -> "Upload from local"
165+
166+
Upload your generated file, `/tmp/aws-tower-layer.json`
167+
168+
You will have a warning, **Click No** to refuse the upgrade on Att&ck v12, stay in v11.
169+
170+
153171
## Findings
154172
155173
Some rules already exists in `config/rules.yaml.sample`, but you can add your own too.

aws_tower_cli.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
# from pdb import set_trace as st
3030

3131
CONSOLE = console.Console()
32-
VERSION = '4.4.2'
32+
VERSION = '4.4.3'
3333

3434
def audit_handler(session, args, meta_types, cache):
3535
"""
@@ -150,6 +150,7 @@ def iam_handler(session, args, cache, csl):
150150
csl,
151151
iam_action_passlist=variables.IAM_ACTION_PASSLIST,
152152
iam_rolename_passlist=variables.IAM_ROLENAME_PASSLIST,
153+
only_dangerous_actions=args.only_dangerous_actions,
153154
verbose=args.verbose)
154155
elif args.source and args.action:
155156
account_id = session.client('sts').get_caller_identity().get('Account')
@@ -170,6 +171,7 @@ def iam_handler(session, args, cache, csl):
170171
csl,
171172
iam_action_passlist=variables.IAM_ACTION_PASSLIST,
172173
iam_rolename_passlist=variables.IAM_ROLENAME_PASSLIST,
174+
only_dangerous_actions=args.only_dangerous_actions,
173175
verbose=args.verbose)
174176

175177
def main(verb, args):
@@ -395,6 +397,10 @@ def main(verb, args):
395397
'-d', '--display',
396398
action='store_true',
397399
help='Display informations about the source ARN')
400+
IAM_PARSER.add_argument(
401+
'--only-dangerous-actions',
402+
action='store_true',
403+
help='Display IAM dangerous actions only')
398404
IAM_PARSER.add_argument(
399405
'-v', '--verbose',
400406
action='store_true',

config/rules.yaml.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
version: 4.4.2
2+
version: 4.4.0
33
types:
44
security_group:
55
description: Check each rule on each security group and on each source

libs/asset_type_ec2.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class EC2(AssetType):
2626
def __init__(self, name: str, private_ip: str, public: bool=False):
2727
super().__init__('EC2', name, public=public)
2828
self.operating_system = 'unknown'
29+
self.operating_system_name = 'unknown'
2930
self.private_ip = private_ip
3031
self.public_ip = ''
3132
self.security_groups = {}
@@ -45,7 +46,7 @@ def report(self, report, brief=False):
4546
asset_report = self.report_brief()
4647
else:
4748
asset_report = {
48-
'OS': self.operating_system,
49+
'OS': f'{self.operating_system} ({self.operating_system_name})',
4950
'PrivateIP': self.private_ip,
5051
'InstanceID': self.instance_id
5152
}
@@ -212,6 +213,10 @@ def scan(ec2, sg_raw, subnets_raw, kp_raw, boto_session, public_only):
212213
ec2_asset.operating_system = ec2_res.Image(ec2['ImageId']).platform_details
213214
except:
214215
pass
216+
try:
217+
ec2_asset.operating_system_name = ec2_res.Image(ec2['ImageId']).name
218+
except:
219+
pass
215220
if 'Tags' in ec2:
216221
ec2_asset.name = get_tag(ec2['Tags'], 'Name')
217222
eks_cluster_name = [ i['Key'].split('/')[-1] for i in ec2['Tags'] if i['Key'].startswith('kubernetes.io/cluster/') ]

libs/iam_scan.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def iam_display(
204204
console,
205205
iam_action_passlist=[],
206206
iam_rolename_passlist=[],
207+
only_dangerous_actions=False,
207208
verbose=False):
208209
"""
209210
Display information about the ARN
@@ -234,11 +235,14 @@ def iam_display(
234235
# Display actions
235236
iam_obj.actions = actions
236237
iam_obj.simplify_actions()
237-
if iam_obj.admin_actions:
238-
console.print(f'[red]Admin actions: {iam_obj.admin_actions}[/red]')
239-
if iam_obj.poweruser_actions and min_rights != 'admin':
240-
console.print(f'[yellow]Poweruser actions: {iam_obj.poweruser_actions}[/yellow]')
241-
console.print(f'ALL Actions: {set(actions)}')
238+
if iam_obj.dangerous_actions:
239+
console.print(f'[red]Dangerous actions: {iam_obj.dangerous_actions}[/red]')
240+
if not only_dangerous_actions:
241+
if iam_obj.admin_actions:
242+
console.print(f'[red]Admin actions: {iam_obj.admin_actions}[/red]')
243+
if iam_obj.poweruser_actions and min_rights != 'admin':
244+
console.print(f'[yellow]Poweruser actions: {iam_obj.poweruser_actions}[/yellow]')
245+
console.print(f'ALL Actions: {set(actions)}')
242246

243247
def get_role_services(role):
244248
"""
@@ -314,6 +318,7 @@ def iam_display_roles(
314318
console,
315319
iam_action_passlist=[],
316320
iam_rolename_passlist=[],
321+
only_dangerous_actions=False,
317322
verbose=False):
318323
"""
319324
Display all roles actions
@@ -327,8 +332,12 @@ def iam_display_roles(
327332
arn=arn,
328333
service=service)
329334
for role in roles:
330-
actions = role.print_actions(min_rights)
331-
if actions:
332-
console.print(actions)
333-
if verbose:
334-
console.print(f'Actions: {role.actions}')
335+
if only_dangerous_actions:
336+
if role.dangerous_actions:
337+
print(f'{role.name}: {role.dangerous_actions}')
338+
else:
339+
actions = role.print_actions(min_rights)
340+
if actions:
341+
console.print(actions)
342+
if verbose:
343+
console.print(f'Actions: {role.actions}')

libs/patterns.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ class Patterns:
9191
'data_sources': ['attribute_value'],
9292
'conditions': ['attribute_value']
9393
},
94+
'has_attribute_contain': {
95+
'data_sources': ['attribute_value'],
96+
'conditions': ['attribute_value']
97+
},
9498
'is_cidr': {
9599
'data_sources': ['source'],
96100
'conditions': ['is_cidr']
@@ -318,6 +322,19 @@ def _check_rule_has_attribute_not_equal(self, data_sources, conditions):
318322
"""
319323
return not self._check_rule_has_attribute_equal(data_sources, conditions)
320324

325+
def _check_rule_has_attribute_contain(self, data_sources, conditions):
326+
"""Check if data_sources['attribute_value'] has the attribute equal to conditions['attribute_value']
327+
Check rule "has_attribute_contain"
328+
329+
:param data_sources: Where we want to find the conditions
330+
:type data_sources: {"attribute_value": mixed}
331+
:param conditions: Attribute value to be equal
332+
:type conditions: {"attribute_value": mixed}
333+
:return: True if one contain the other
334+
:rtype: bool
335+
"""
336+
return data_sources['attribute_value'].lower() in conditions['attribute_value'].lower() or conditions['attribute_value'].lower() in data_sources['attribute_value'].lower()
337+
321338
def _check_rule_in(self, data_sources, conditions):
322339
"""Check if conditions['data_element'] may be found in data_sources['data_list']
323340
Check rule "in"

libs/tools.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ def generate_layer(rules_path):
135135
for rule in [
136136
*rules['types']['security_group']['findings'],
137137
*rules['types']['attributes']['findings']]:
138-
if 'techniques' not in rule or i not in rule['techniques']:
138+
if 'metadata' not in rule or i not in rule['metadata']:
139139
continue
140-
for tech_id in rule['techniques'][i]:
140+
for tech_id in rule['metadata'][i]:
141141
tech_ids.add(tech_id)
142142
for tech_id in tech_ids:
143143
# Check if technique is not already in the layer
@@ -400,7 +400,8 @@ def search_filter_in(asset, filter_str):
400400
if asset.get_type() == 'RDS' and '==' in asset.engine:
401401
is_found = asset.engine.split('==')[1].startswith(version)
402402
elif filter_str.startswith('os:') and hasattr(asset, 'operating_system'):
403-
is_found = asset.operating_system.lower().startswith(filter_str.split(':')[1])
403+
os_name = f'{asset.operating_system}+{asset.operating_system_name}'.lower()
404+
is_found = os_name in filter_str.split(':')[1] or filter_str.split(':')[1] in os_name
404405
else:
405406
if filter_str in asset.name.lower():
406407
return True

0 commit comments

Comments
 (0)