cartography 0.116.0__py3-none-any.whl → 0.117.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cartography might be problematic. Click here for more details.

@@ -52,11 +52,40 @@ class ECRImageHasLayerRel(CartographyRelSchema):
52
52
  properties: ECRImageHasLayerRelProperties = ECRImageHasLayerRelProperties()
53
53
 
54
54
 
55
+ @dataclass(frozen=True)
56
+ class ECRImageToParentImageRelProperties(CartographyRelProperties):
57
+ lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
58
+ from_attestation: PropertyRef = PropertyRef("from_attestation")
59
+ parent_image_uri: PropertyRef = PropertyRef("parent_image_uri")
60
+ confidence: PropertyRef = PropertyRef("confidence")
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class ECRImageToParentImageRel(CartographyRelSchema):
65
+ """
66
+ Relationship from an ECRImage to its parent ECRImage (BUILT_FROM).
67
+ This relationship is created when provenance attestations explicitly specify the parent image.
68
+ """
69
+
70
+ target_node_label: str = "ECRImage"
71
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
72
+ {"digest": PropertyRef("parent_image_digest")},
73
+ )
74
+ direction: LinkDirection = LinkDirection.OUTWARD
75
+ rel_label: str = "BUILT_FROM"
76
+ properties: ECRImageToParentImageRelProperties = (
77
+ ECRImageToParentImageRelProperties()
78
+ )
79
+
80
+
55
81
  @dataclass(frozen=True)
56
82
  class ECRImageSchema(CartographyNodeSchema):
57
83
  label: str = "ECRImage"
58
84
  properties: ECRImageNodeProperties = ECRImageNodeProperties()
59
85
  sub_resource_relationship: ECRImageToAWSAccountRel = ECRImageToAWSAccountRel()
60
86
  other_relationships: OtherRelationships = OtherRelationships(
61
- [ECRImageHasLayerRel()],
87
+ [
88
+ ECRImageHasLayerRel(),
89
+ ECRImageToParentImageRel(),
90
+ ],
62
91
  )
@@ -0,0 +1,55 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+
4
+ from cartography.models.core.common import PropertyRef
5
+ from cartography.models.core.nodes import CartographyNodeProperties
6
+ from cartography.models.core.nodes import CartographyNodeSchema
7
+ from cartography.models.core.relationships import CartographyRelProperties
8
+ from cartography.models.core.relationships import CartographyRelSchema
9
+ from cartography.models.core.relationships import LinkDirection
10
+ from cartography.models.core.relationships import make_target_node_matcher
11
+ from cartography.models.core.relationships import TargetNodeMatcher
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ # --- Node Definitions ---
17
+ @dataclass(frozen=True)
18
+ class AzureContainerInstanceProperties(CartographyNodeProperties):
19
+ id: PropertyRef = PropertyRef("id")
20
+ name: PropertyRef = PropertyRef("name")
21
+ location: PropertyRef = PropertyRef("location")
22
+ type: PropertyRef = PropertyRef("type")
23
+ provisioning_state: PropertyRef = PropertyRef("provisioning_state")
24
+ ip_address: PropertyRef = PropertyRef("ip_address")
25
+ os_type: PropertyRef = PropertyRef("os_type")
26
+ lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
27
+
28
+
29
+ # --- Relationship Definitions ---
30
+ @dataclass(frozen=True)
31
+ class AzureContainerInstanceToSubscriptionRelProperties(CartographyRelProperties):
32
+ lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class AzureContainerInstanceToSubscriptionRel(CartographyRelSchema):
37
+ target_node_label: str = "AzureSubscription"
38
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
39
+ {"id": PropertyRef("AZURE_SUBSCRIPTION_ID", set_in_kwargs=True)},
40
+ )
41
+ direction: LinkDirection = LinkDirection.INWARD
42
+ rel_label: str = "RESOURCE"
43
+ properties: AzureContainerInstanceToSubscriptionRelProperties = (
44
+ AzureContainerInstanceToSubscriptionRelProperties()
45
+ )
46
+
47
+
48
+ # --- Main Schema ---
49
+ @dataclass(frozen=True)
50
+ class AzureContainerInstanceSchema(CartographyNodeSchema):
51
+ label: str = "AzureContainerInstance"
52
+ properties: AzureContainerInstanceProperties = AzureContainerInstanceProperties()
53
+ sub_resource_relationship: AzureContainerInstanceToSubscriptionRel = (
54
+ AzureContainerInstanceToSubscriptionRel()
55
+ )
@@ -0,0 +1,51 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+
4
+ from cartography.models.core.common import PropertyRef
5
+ from cartography.models.core.nodes import CartographyNodeProperties
6
+ from cartography.models.core.nodes import CartographyNodeSchema
7
+ from cartography.models.core.relationships import CartographyRelProperties
8
+ from cartography.models.core.relationships import CartographyRelSchema
9
+ from cartography.models.core.relationships import LinkDirection
10
+ from cartography.models.core.relationships import make_target_node_matcher
11
+ from cartography.models.core.relationships import TargetNodeMatcher
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class AzureDataLakeFileSystemProperties(CartographyNodeProperties):
18
+ id: PropertyRef = PropertyRef("id")
19
+ name: PropertyRef = PropertyRef("name")
20
+ public_access: PropertyRef = PropertyRef("public_access")
21
+ last_modified_time: PropertyRef = PropertyRef("last_modified_time")
22
+ has_immutability_policy: PropertyRef = PropertyRef("has_immutability_policy")
23
+ has_legal_hold: PropertyRef = PropertyRef("has_legal_hold")
24
+ lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class AzureDataLakeFileSystemToStorageAccountRelProperties(CartographyRelProperties):
29
+ lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True)
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class AzureDataLakeFileSystemToStorageAccountRel(CartographyRelSchema):
34
+ target_node_label: str = "AzureStorageAccount"
35
+ target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
36
+ {"id": PropertyRef("STORAGE_ACCOUNT_ID", set_in_kwargs=True)},
37
+ )
38
+ direction: LinkDirection = LinkDirection.INWARD
39
+ rel_label: str = "CONTAINS"
40
+ properties: AzureDataLakeFileSystemToStorageAccountRelProperties = (
41
+ AzureDataLakeFileSystemToStorageAccountRelProperties()
42
+ )
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class AzureDataLakeFileSystemSchema(CartographyNodeSchema):
47
+ label: str = "AzureDataLakeFileSystem"
48
+ properties: AzureDataLakeFileSystemProperties = AzureDataLakeFileSystemProperties()
49
+ sub_resource_relationship: AzureDataLakeFileSystemToStorageAccountRel = (
50
+ AzureDataLakeFileSystemToStorageAccountRel()
51
+ )
cartography/rules/cli.py CHANGED
@@ -47,19 +47,21 @@ def complete_frameworks_with_all(incomplete: str) -> Generator[str, None, None]:
47
47
 
48
48
  def complete_requirements(
49
49
  ctx: typer.Context, incomplete: str
50
- ) -> Generator[str, None, None]:
51
- """Autocomplete requirement IDs based on selected framework."""
50
+ ) -> Generator[tuple[str, str], None, None]:
51
+ """Autocomplete requirement IDs with descriptions based on selected framework."""
52
52
  framework = ctx.params.get("framework")
53
53
  if not framework or framework not in FRAMEWORKS:
54
54
  return
55
55
 
56
56
  for req in FRAMEWORKS[framework].requirements:
57
57
  if req.id.lower().startswith(incomplete.lower()):
58
- yield req.id
58
+ yield (req.id, req.name)
59
59
 
60
60
 
61
- def complete_facts(ctx: typer.Context, incomplete: str) -> Generator[str, None, None]:
62
- """Autocomplete fact IDs based on selected framework and requirement."""
61
+ def complete_facts(
62
+ ctx: typer.Context, incomplete: str
63
+ ) -> Generator[tuple[str, str], None, None]:
64
+ """Autocomplete fact IDs with descriptions based on selected framework and requirement."""
63
65
  framework = ctx.params.get("framework")
64
66
  requirement_id = ctx.params.get("requirement")
65
67
 
@@ -73,7 +75,7 @@ def complete_facts(ctx: typer.Context, incomplete: str) -> Generator[str, None,
73
75
  if req.id.lower() == requirement_id.lower():
74
76
  for fact in req.facts:
75
77
  if fact.id.lower().startswith(incomplete.lower()):
76
- yield fact.id
78
+ yield (fact.id, fact.name)
77
79
  break
78
80
 
79
81
 
@@ -1,4 +1,7 @@
1
1
  # MITRE ATT&CK Framework
2
+ from cartography.rules.data.frameworks.mitre_attack.requirements.t1098_account_manipulation import (
3
+ t1098,
4
+ )
2
5
  from cartography.rules.data.frameworks.mitre_attack.requirements.t1190_exploit_public_facing_application import (
3
6
  t1190,
4
7
  )
@@ -9,6 +12,9 @@ mitre_attack_framework = Framework(
9
12
  name="MITRE ATT&CK",
10
13
  description="Comprehensive security assessment framework based on MITRE ATT&CK tactics and techniques",
11
14
  version="1.0",
12
- requirements=(t1190,),
15
+ requirements=(
16
+ t1098,
17
+ t1190,
18
+ ),
13
19
  source_url="https://attack.mitre.org/",
14
20
  )
@@ -0,0 +1,317 @@
1
+ from cartography.rules.spec.model import Fact
2
+ from cartography.rules.spec.model import Module
3
+ from cartography.rules.spec.model import Requirement
4
+
5
+ # AWS
6
+ _aws_account_manipulation_permissions = Fact(
7
+ id="aws_account_manipulation_permissions",
8
+ name="IAM Principals with Account Creation and Modification Permissions",
9
+ description=(
10
+ "AWS IAM users and roles with permissions to create, modify, or delete IAM "
11
+ "accounts and their associated policies."
12
+ ),
13
+ cypher_query="""
14
+ MATCH (a:AWSAccount)-[:RESOURCE]->(principal:AWSPrincipal)
15
+ MATCH (principal)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
16
+ WHERE NOT principal.name STARTS WITH 'AWSServiceRole'
17
+ AND NOT principal.name CONTAINS 'QuickSetup'
18
+ AND principal.name <> 'OrganizationAccountAccessRole'
19
+ AND stmt.effect = 'Allow'
20
+ WITH a, principal, stmt,
21
+ // Return labels that are not the general "AWSPrincipal" label
22
+ [label IN labels(principal) WHERE label <> 'AWSPrincipal'][0] AS principal_type,
23
+ // Define the list of IAM actions to match on
24
+ [p IN ['iam:Create','iam:Attach','iam:Put','iam:Update','iam:Add'] |
25
+ p] AS patterns
26
+ WITH a, principal, principal_type, stmt,
27
+ // Filter on the desired IAM actions
28
+ [action IN stmt.action
29
+ WHERE ANY(prefix IN patterns WHERE action STARTS WITH prefix)
30
+ OR action = 'iam:*'
31
+ OR action = '*'
32
+ ] AS matched_actions
33
+ // Return only statement actions that we matched on
34
+ WHERE size(matched_actions) > 0
35
+ UNWIND matched_actions AS action
36
+ RETURN DISTINCT a.name AS account,
37
+ principal.name AS principal_name,
38
+ principal.arn AS principal_arn,
39
+ principal_type,
40
+ collect(action) as action,
41
+ stmt.resource AS resource
42
+ ORDER BY account, principal_name
43
+ """,
44
+ cypher_visual_query="""
45
+ MATCH p = (a:AWSAccount)-[:RESOURCE]->(principal:AWSPrincipal)
46
+ MATCH p1 = (principal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
47
+ WHERE NOT principal.name STARTS WITH 'AWSServiceRole'
48
+ AND NOT principal.name CONTAINS 'QuickSetup'
49
+ AND NOT principal.name = 'OrganizationAccountAccessRole'
50
+ AND stmt.effect = 'Allow'
51
+ AND ANY(action IN stmt.action WHERE
52
+ action STARTS WITH 'iam:Create'
53
+ OR action STARTS WITH 'iam:Attach'
54
+ OR action STARTS WITH 'iam:Put'
55
+ OR action STARTS WITH 'iam:Update'
56
+ OR action STARTS WITH 'iam:Add'
57
+ OR action = 'iam:*'
58
+ OR action = '*'
59
+ )
60
+ RETURN *
61
+ """,
62
+ module=Module.AWS,
63
+ )
64
+
65
+ _aws_trust_relationship_manipulation = Fact(
66
+ id="aws_trust_relationship_manipulation",
67
+ name="Roles with Cross-Account Trust Relationship Modification Capabilities",
68
+ description=(
69
+ "AWS IAM principals with permissions to modify role trust policies "
70
+ "(specifically AssumeRolePolicyDocuments)."
71
+ ),
72
+ cypher_query="""
73
+ MATCH (a:AWSAccount)-[:RESOURCE]->(principal:AWSPrincipal)
74
+ MATCH (principal)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
75
+ OPTIONAL MATCH (groupmember:AWSUser)
76
+ WHERE NOT principal.name STARTS WITH 'AWSServiceRole'
77
+ AND NOT principal.name CONTAINS 'QuickSetup'
78
+ AND principal.name <> 'OrganizationAccountAccessRole'
79
+ AND stmt.effect = 'Allow'
80
+ WITH a, principal, stmt,
81
+ [label IN labels(principal) WHERE label <> 'AWSPrincipal'][0] AS principal_type,
82
+ ['iam:UpdateAssumeRolePolicy','iam:CreateRole'] AS patterns
83
+ WITH a, principal, principal_type, stmt,
84
+ [action IN stmt.action
85
+ WHERE ANY(p IN patterns WHERE action = p)
86
+ OR action = 'iam:*' OR action = '*'
87
+ ] AS matched_actions
88
+ WHERE size(matched_actions) > 0
89
+ UNWIND matched_actions AS action
90
+ RETURN DISTINCT a.name AS account,
91
+ principal.name AS principal_name,
92
+ principal.arn AS principal_arn,
93
+ principal_type,
94
+ collect(distinct action) as action,
95
+ stmt.resource AS resource
96
+ ORDER BY account, principal_name
97
+ """,
98
+ cypher_visual_query="""
99
+ MATCH p = (a:AWSAccount)-[:RESOURCE]->(principal:AWSPrincipal)
100
+ MATCH p1 = (principal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
101
+ MATCH (principal)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
102
+ WHERE NOT principal.name STARTS WITH 'AWSServiceRole'
103
+ AND principal.name <> 'OrganizationAccountAccessRole'
104
+ AND stmt.effect = 'Allow'
105
+ RETURN *
106
+ """,
107
+ module=Module.AWS,
108
+ )
109
+
110
+ _aws_service_account_manipulation_via_ec2 = Fact(
111
+ id="aws_service_account_manipulation_via_ec2",
112
+ name="Service Resources with Account Manipulation Through Instance Profiles",
113
+ description=(
114
+ "AWS EC2 instances with attached IAM roles that can manipulate other AWS accounts. "
115
+ "Also indicates whether the instance is internet-exposed."
116
+ ),
117
+ cypher_query="""
118
+ MATCH (a:AWSAccount)-[:RESOURCE]->(ec2:EC2Instance)
119
+ MATCH (ec2)-[:INSTANCE_PROFILE]->(profile:AWSInstanceProfile)
120
+ MATCH (profile)-[:ASSOCIATED_WITH]->(role:AWSRole)
121
+ MATCH (role)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
122
+ WHERE stmt.effect = 'Allow'
123
+ WITH a, ec2, role,
124
+ // Define the list of IAM actions to match on
125
+ ['iam:Create', 'iam:Attach', 'iam:Put', 'iam:Update'] AS patterns,
126
+ // Filter on the desired IAM actions
127
+ [action IN stmt.action
128
+ WHERE ANY(p IN ['iam:Create','iam:Attach','iam:Put','iam:Update', 'iam:Add'] WHERE action STARTS WITH p)
129
+ OR action = 'iam:*'
130
+ OR action = '*'
131
+ ] AS actions
132
+ // For the instances that are internet open, include the SG and rules
133
+ OPTIONAL MATCH (ec2{exposed_internet:True})-[:MEMBER_OF_EC2_SECURITY_GROUP]->(sg:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP]-(ip:IpPermissionInbound)
134
+ // Return only statement actions that we matched on
135
+ WHERE size(actions) > 0
136
+ UNWIND actions AS flat_action
137
+ WITH a, ec2, role, sg, ip,
138
+ collect(DISTINCT flat_action) AS actions
139
+ RETURN DISTINCT a.name AS account,
140
+ a.id as account_id,
141
+ ec2.instanceid AS instance_id,
142
+ ec2.exposed_internet AS internet_accessible,
143
+ ec2.publicipaddress as public_ip_address,
144
+ role.name AS role_name,
145
+ collect(actions) as action,
146
+ ip.fromport as from_port,
147
+ ip.toport as to_port
148
+ ORDER BY account, instance_id, internet_accessible, from_port
149
+ """,
150
+ cypher_visual_query="""
151
+ MATCH p = (a:AWSAccount)-[:RESOURCE]->(ec2:EC2Instance)
152
+ MATCH p1 = (ec2)-[:INSTANCE_PROFILE]->(profile:AWSInstanceProfile)
153
+ MATCH p2 = (profile)-[:ASSOCIATED_WITH]->(role:AWSRole)
154
+ MATCH p3 = (role)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
155
+ WHERE stmt.effect = 'Allow'
156
+ AND ANY(action IN stmt.action WHERE
157
+ action STARTS WITH 'iam:Create'
158
+ OR action STARTS WITH 'iam:Attach'
159
+ OR action STARTS WITH 'iam:Put'
160
+ OR action STARTS WITH 'iam:Update'
161
+ OR action STARTS WITH 'iam:Add'
162
+ OR action = 'iam:*'
163
+ OR action = '*'
164
+ )
165
+ WITH p, p1, p2, p3, ec2
166
+ // Include the SG and rules for the instances that are internet open
167
+ MATCH p4=(ec2{exposed_internet: true})-[:MEMBER_OF_EC2_SECURITY_GROUP]->(sg:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP]-(ip:IpPermissionInbound)
168
+ RETURN *
169
+ """,
170
+ module=Module.AWS,
171
+ )
172
+
173
+ _aws_service_account_manipulation_via_lambda = Fact(
174
+ id="aws_service_account_manipulation",
175
+ name="Service Resources with Account Manipulation Through Lambda Roles",
176
+ description=(
177
+ "AWS Lambda functions with IAM roles that can manipulate other AWS accounts."
178
+ ),
179
+ cypher_query="""
180
+ // Find Lambda functions with account manipulation capabilities
181
+ MATCH (a:AWSAccount)-[:RESOURCE]->(lambda:AWSLambda)
182
+ MATCH (lambda)-[:STS_ASSUMEROLE_ALLOW]->(role:AWSRole)
183
+ MATCH (role)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
184
+ WHERE stmt.effect = 'Allow'
185
+ WITH a, lambda, role, stmt,
186
+ // Define the list of IAM actions to match on
187
+ ['iam:Create', 'iam:Attach', 'iam:Put', 'iam:Update'] AS patterns,
188
+ // Filter on the desired IAM actions
189
+ [action IN stmt.action
190
+ WHERE ANY(p IN ['iam:Create', 'iam:Attach', 'iam:Put', 'iam:Update', 'iam:Add'] WHERE action STARTS WITH p)
191
+ OR action = 'iam:*'
192
+ OR action = '*'
193
+ ] AS actions
194
+ // Return only statement actions that we matched on
195
+ WHERE size(actions) > 0
196
+ UNWIND actions AS flat_action
197
+ WITH a, lambda, role, stmt,
198
+ collect(DISTINCT flat_action) AS actions
199
+ RETURN DISTINCT a.name AS account,
200
+ a.id as account_id,
201
+ lambda.arn AS arn,
202
+ lambda.description AS description,
203
+ lambda.anonymous_access AS internet_accessible,
204
+ role.name AS role_name,
205
+ actions
206
+ ORDER BY account, arn, internet_accessible
207
+ """,
208
+ cypher_visual_query="""
209
+ MATCH p = (a:AWSAccount)-[:RESOURCE]->(lambda:AWSLambda)
210
+ MATCH p1 = (lambda)-[:STS_ASSUMEROLE_ALLOW]->(role:AWSRole)
211
+ MATCH p2 = (role)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
212
+ WHERE stmt.effect = 'Allow'
213
+ AND ANY(action IN stmt.action WHERE
214
+ action STARTS WITH 'iam:Create'
215
+ OR action STARTS WITH 'iam:Attach'
216
+ OR action STARTS WITH 'iam:Put'
217
+ OR action STARTS WITH 'iam:Update'
218
+ OR action STARTS WITH 'iam:Add'
219
+ OR action = 'iam:*'
220
+ OR action = '*'
221
+ )
222
+ RETURN *
223
+ """,
224
+ module=Module.AWS,
225
+ )
226
+
227
+ _aws_policy_manipulation_capabilities = Fact(
228
+ id="aws_policy_manipulation_capabilities",
229
+ name="Principals with IAM Policy Creation and Modification Capabilities",
230
+ description=(
231
+ "AWS IAM principals that can create, modify, or attach IAM policies to other principals. "
232
+ ),
233
+ cypher_query="""
234
+ MATCH (a:AWSAccount)-[:RESOURCE]->(principal:AWSPrincipal)
235
+ MATCH (principal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
236
+ WHERE NOT principal.name STARTS WITH 'AWSServiceRole'
237
+ AND NOT principal.name CONTAINS 'QuickSetup'
238
+ AND principal.name <> 'OrganizationAccountAccessRole'
239
+ AND stmt.effect = 'Allow'
240
+
241
+ WITH a, principal, stmt,
242
+ // Return labels that are not the general "AWSPrincipal" label
243
+ [label IN labels(principal) WHERE label <> 'AWSPrincipal'][0] AS principal_type,
244
+ // Define the list of IAM actions to match on
245
+ [p IN
246
+ ['iam:CreatePolicy', 'iam:CreatePolicyVersion', 'iam:AttachUserPolicy', 'iam:AttachRolePolicy', 'iam:AttachGroupPolicy',
247
+ 'iam:AttachRolePolicy', 'iam:AttachGroupPolicy', 'iam:DetachUserPolicy', 'iam:DetachRolePolicy', 'iam:DetachGroupPolicy',
248
+ 'iam:PutUserPolicy', 'iam:PutRolePolicy', 'iam:PutGroupPolicy'] |
249
+ p] AS patterns
250
+
251
+ // Return only statement actions that we matched on
252
+ WITH a, principal, principal_type, stmt,
253
+ [action IN stmt.action
254
+ WHERE ANY(p IN patterns WHERE action = p)
255
+ OR action = 'iam:*' OR action = '*'
256
+ ] AS matched_actions
257
+ WHERE size(matched_actions) > 0
258
+ UNWIND matched_actions AS action
259
+ RETURN DISTINCT a.name AS account,
260
+ principal.name AS principal_name,
261
+ principal.arn AS principal_arn,
262
+ principal_type,
263
+ collect(action) as action,
264
+ stmt.resource AS resource
265
+ ORDER BY account, principal_name
266
+ """,
267
+ cypher_visual_query="""
268
+ MATCH p1=(a:AWSAccount)-[:RESOURCE]->(principal:AWSPrincipal)
269
+ MATCH p2=(principal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement)
270
+ WHERE NOT principal.name STARTS WITH 'AWSServiceRole'
271
+ AND NOT principal.name CONTAINS 'QuickSetup'
272
+ AND principal.name <> 'OrganizationAccountAccessRole'
273
+ AND stmt.effect = 'Allow'
274
+ AND ANY(action IN stmt.action WHERE
275
+ action CONTAINS 'iam:CreatePolicy' OR action CONTAINS 'iam:CreatePolicyVersion'
276
+ OR action CONTAINS 'iam:AttachUserPolicy' OR action CONTAINS 'iam:AttachRolePolicy'
277
+ OR action CONTAINS 'iam:AttachGroupPolicy' OR action CONTAINS 'iam:DetachUserPolicy'
278
+ OR action CONTAINS 'iam:DetachRolePolicy' OR action CONTAINS 'iam:DetachGroupPolicy'
279
+ OR action CONTAINS 'iam:PutUserPolicy' OR action CONTAINS 'iam:PutRolePolicy'
280
+ OR action CONTAINS 'iam:PutGroupPolicy' OR action = 'iam:*' OR action = '*'
281
+ )
282
+ RETURN *
283
+ """,
284
+ module=Module.AWS,
285
+ )
286
+
287
+
288
+ t1098 = Requirement(
289
+ id="t1098",
290
+ name="Account Manipulation",
291
+ description=(
292
+ "Adversaries may manipulate accounts to maintain or elevate access to victim systems. "
293
+ "Activity that subverts security policies. For example in cloud this is "
294
+ "updating IAM policies or adding new global admins."
295
+ ),
296
+ target_assets="Identities that can manipulate other identities",
297
+ facts=(
298
+ # AWS
299
+ _aws_account_manipulation_permissions,
300
+ _aws_trust_relationship_manipulation,
301
+ _aws_service_account_manipulation_via_ec2,
302
+ _aws_service_account_manipulation_via_lambda,
303
+ _aws_policy_manipulation_capabilities,
304
+ ),
305
+ requirement_url="https://attack.mitre.org/techniques/T1098/",
306
+ attributes={
307
+ "tactic": "persistence,privilege_escalation",
308
+ "technique_id": "T1098",
309
+ "services": [
310
+ "iam",
311
+ "sts",
312
+ "ec2",
313
+ "lambda",
314
+ ],
315
+ "providers": ["AWS"],
316
+ },
317
+ )
@@ -116,6 +116,7 @@ t1190 = Requirement(
116
116
  id="t1190",
117
117
  name="Exploit Public-Facing Application",
118
118
  description="Adversaries may attempt to take advantage of a weakness in an Internet-facing computer or program using software, data, or commands in order to cause unintended or unanticipated behavior.",
119
+ target_assets="Compute and storage resources that are accessible from the public internet",
119
120
  facts=(
120
121
  # AWS
121
122
  _aws_ec2_instance_internet_exposed,
@@ -58,9 +58,21 @@ class Requirement:
58
58
  """
59
59
 
60
60
  id: str
61
+ """A unique identifier for the requirement, e.g. T1098 in MITRE ATT&CK."""
61
62
  name: str
63
+ """A brief name for the requirement, e.g. "Account Manipulation"."""
62
64
  description: str
65
+ """A brief description of the requirement."""
66
+ target_assets: str
67
+ """
68
+ A short description of the assets that this requirement is related to. E.g. "Cloud
69
+ identities that can manipulate other identities". This field is used as
70
+ documentation: `description` tells us information about the requirement;
71
+ `target_assets` tells us what specific objects in cartography we will search for to
72
+ find Facts related to the requirement.
73
+ """
63
74
  facts: tuple[Fact, ...]
75
+ """The facts that are related to this requirement."""
64
76
  attributes: dict[str, Any] | None = None
65
77
  """
66
78
  Metadata attributes for the requirement. Example:
@@ -74,6 +86,7 @@ class Requirement:
74
86
  ```
75
87
  """
76
88
  requirement_url: str | None = None
89
+ """A URL reference to the requirement in the framework, e.g. https://attack.mitre.org/techniques/T1098/"""
77
90
 
78
91
 
79
92
  @dataclass(frozen=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cartography
3
- Version: 0.116.0
3
+ Version: 0.117.0
4
4
  Summary: Explore assets and their relationships across your technical infrastructure.
5
5
  Maintainer: Cartography Contributors
6
6
  License-Expression: Apache-2.0
@@ -42,6 +42,7 @@ Requires-Dist: python-digitalocean>=1.16.0
42
42
  Requires-Dist: adal>=1.2.4
43
43
  Requires-Dist: azure-cli-core>=2.26.0
44
44
  Requires-Dist: azure-mgmt-compute>=5.0.0
45
+ Requires-Dist: azure-mgmt-containerinstance>=10.0.0
45
46
  Requires-Dist: azure-mgmt-resource>=10.2.0
46
47
  Requires-Dist: azure-mgmt-cosmosdb>=6.0.0
47
48
  Requires-Dist: azure-mgmt-web>=7.0.0
@@ -60,6 +61,7 @@ Requires-Dist: duo-client
60
61
  Requires-Dist: cloudflare<5.0.0,>=4.1.0
61
62
  Requires-Dist: scaleway>=2.9.0
62
63
  Requires-Dist: google-cloud-resource-manager>=1.14.2
64
+ Requires-Dist: types-aiobotocore-ecr
63
65
  Requires-Dist: typer>=0.9.0
64
66
  Dynamic: license-file
65
67
 
@@ -101,7 +103,7 @@ You can learn more about the story behind Cartography in our [presentation at BS
101
103
  - [Keycloak](https://cartography-cncf.github.io/cartography/modules/keycloak/index.html) - Realms, Users, Groups, Roles, Scopes, Clients, IdentityProviders, Authentication Flows, Authentication Executions, Organizations, Organization Domains
102
104
  - [Kubernetes](https://cartography-cncf.github.io/cartography/modules/kubernetes/index.html) - Cluster, Namespace, Service, Pod, Container, ServiceAccount, Role, RoleBinding, ClusterRole, ClusterRoleBinding, OIDCProvider
103
105
  - [Lastpass](https://cartography-cncf.github.io/cartography/modules/lastpass/index.html) - users
104
- - [Microsoft Azure](https://cartography-cncf.github.io/cartography/modules/azure/index.html) - App Service, CosmosDB, Functions, Logic Apps, Resource Group, SQL, Storage, Virtual Machine
106
+ - [Microsoft Azure](https://cartography-cncf.github.io/cartography/modules/azure/index.html) - App Service, Container Instance, CosmosDB, Functions, Logic Apps, Resource Group, SQL, Storage, Virtual Machine
105
107
  - [Microsoft Entra ID](https://cartography-cncf.github.io/cartography/modules/entra/index.html) - Users, Groups, Applications, OUs, App Roles, federation to AWS Identity Center
106
108
  - [NIST CVE](https://cartography-cncf.github.io/cartography/modules/cve/index.html) - Common Vulnerabilities and Exposures (CVE) data from NIST database
107
109
  - [Okta](https://cartography-cncf.github.io/cartography/modules/okta/index.html) - users, groups, organizations, roles, applications, factors, trusted origins, reply URIs, federation to AWS roles, federation to AWS Identity Center