exploitgraph 1.0.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.
Files changed (42) hide show
  1. core/__init__.py +0 -0
  2. core/attack_graph.py +83 -0
  3. core/aws_client.py +284 -0
  4. core/config.py +83 -0
  5. core/console.py +469 -0
  6. core/context_engine.py +172 -0
  7. core/correlator.py +476 -0
  8. core/http_client.py +243 -0
  9. core/logger.py +97 -0
  10. core/module_loader.py +69 -0
  11. core/risk_engine.py +47 -0
  12. core/session_manager.py +254 -0
  13. exploitgraph-1.0.0.dist-info/METADATA +429 -0
  14. exploitgraph-1.0.0.dist-info/RECORD +42 -0
  15. exploitgraph-1.0.0.dist-info/WHEEL +5 -0
  16. exploitgraph-1.0.0.dist-info/entry_points.txt +2 -0
  17. exploitgraph-1.0.0.dist-info/licenses/LICENSE +21 -0
  18. exploitgraph-1.0.0.dist-info/top_level.txt +2 -0
  19. modules/__init__.py +0 -0
  20. modules/base.py +82 -0
  21. modules/cloud/__init__.py +0 -0
  22. modules/cloud/aws_credential_validator.py +340 -0
  23. modules/cloud/azure_enum.py +289 -0
  24. modules/cloud/cloudtrail_analyzer.py +494 -0
  25. modules/cloud/gcp_enum.py +272 -0
  26. modules/cloud/iam_enum.py +321 -0
  27. modules/cloud/iam_privilege_escalation.py +515 -0
  28. modules/cloud/metadata_check.py +315 -0
  29. modules/cloud/s3_enum.py +469 -0
  30. modules/discovery/__init__.py +0 -0
  31. modules/discovery/http_enum.py +235 -0
  32. modules/discovery/subdomain_enum.py +260 -0
  33. modules/exploitation/__init__.py +0 -0
  34. modules/exploitation/api_exploit.py +403 -0
  35. modules/exploitation/jwt_attack.py +346 -0
  36. modules/exploitation/ssrf_scanner.py +258 -0
  37. modules/reporting/__init__.py +0 -0
  38. modules/reporting/html_report.py +446 -0
  39. modules/reporting/json_export.py +107 -0
  40. modules/secrets/__init__.py +0 -0
  41. modules/secrets/file_secrets.py +358 -0
  42. modules/secrets/git_secrets.py +267 -0
@@ -0,0 +1,515 @@
1
+ """
2
+ ExploitGraph Module: IAM Privilege Escalation Scanner
3
+ Category: cloud
4
+
5
+ Enumerates IAM permissions and detects privilege escalation paths.
6
+ Based on Rhino Security Labs research on AWS privilege escalation methods.
7
+
8
+ Detects:
9
+ - Direct policy modifications (PutUserPolicy, AttachUserPolicy)
10
+ - Role assumption chains (PassRole → AssumeRole)
11
+ - Service-linked escalation (Lambda, EC2, CloudFormation)
12
+ - Wildcard permissions
13
+ - Cross-account trust misconfigurations
14
+
15
+ All operations are READ-ONLY enumeration.
16
+
17
+ References:
18
+ https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
19
+
20
+ MITRE: T1078.004, T1548, T1134.001
21
+ """
22
+ from __future__ import annotations
23
+ import json
24
+ from typing import TYPE_CHECKING
25
+
26
+ from modules.base import BaseModule, ModuleResult
27
+
28
+ if TYPE_CHECKING:
29
+ from core.session_manager import Session
30
+
31
+ # Known privilege escalation paths (method → required permissions)
32
+ # Source: Rhino Security Labs IAM Privilege Escalation research
33
+ PRIVESC_PATHS = [
34
+ {
35
+ "name": "CreateNewPolicyVersion",
36
+ "permissions": ["iam:CreatePolicyVersion"],
37
+ "severity": "CRITICAL",
38
+ "description": "Create new policy version with admin permissions",
39
+ "mitre": "T1548",
40
+ },
41
+ {
42
+ "name": "SetExistingDefaultPolicyVersion",
43
+ "permissions": ["iam:SetDefaultPolicyVersion"],
44
+ "severity": "CRITICAL",
45
+ "description": "Set existing policy version with admin permissions as default",
46
+ "mitre": "T1548",
47
+ },
48
+ {
49
+ "name": "CreateAccessKey",
50
+ "permissions": ["iam:CreateAccessKey"],
51
+ "severity": "CRITICAL",
52
+ "description": "Create access key for another IAM user (including admins)",
53
+ "mitre": "T1078.004",
54
+ },
55
+ {
56
+ "name": "CreateLoginProfile",
57
+ "permissions": ["iam:CreateLoginProfile"],
58
+ "severity": "CRITICAL",
59
+ "description": "Create console login for a user without one",
60
+ "mitre": "T1078.004",
61
+ },
62
+ {
63
+ "name": "UpdateLoginProfile",
64
+ "permissions": ["iam:UpdateLoginProfile"],
65
+ "severity": "CRITICAL",
66
+ "description": "Update console login password for any IAM user",
67
+ "mitre": "T1078.004",
68
+ },
69
+ {
70
+ "name": "AttachUserPolicy",
71
+ "permissions": ["iam:AttachUserPolicy"],
72
+ "severity": "CRITICAL",
73
+ "description": "Attach AdministratorAccess policy to self or another user",
74
+ "mitre": "T1548",
75
+ },
76
+ {
77
+ "name": "AttachGroupPolicy",
78
+ "permissions": ["iam:AttachGroupPolicy"],
79
+ "severity": "CRITICAL",
80
+ "description": "Attach admin policy to a group the attacker is in",
81
+ "mitre": "T1548",
82
+ },
83
+ {
84
+ "name": "AttachRolePolicy",
85
+ "permissions": ["iam:AttachRolePolicy"],
86
+ "severity": "CRITICAL",
87
+ "description": "Attach admin policy to an assumable role",
88
+ "mitre": "T1548",
89
+ },
90
+ {
91
+ "name": "PutUserPolicy",
92
+ "permissions": ["iam:PutUserPolicy"],
93
+ "severity": "CRITICAL",
94
+ "description": "Add inline policy with admin permissions to self",
95
+ "mitre": "T1548",
96
+ },
97
+ {
98
+ "name": "PutGroupPolicy",
99
+ "permissions": ["iam:PutGroupPolicy"],
100
+ "severity": "CRITICAL",
101
+ "description": "Add inline admin policy to a group the attacker is in",
102
+ "mitre": "T1548",
103
+ },
104
+ {
105
+ "name": "PutRolePolicy",
106
+ "permissions": ["iam:PutRolePolicy"],
107
+ "severity": "CRITICAL",
108
+ "description": "Add inline admin policy to an assumable role",
109
+ "mitre": "T1548",
110
+ },
111
+ {
112
+ "name": "AddUserToGroup",
113
+ "permissions": ["iam:AddUserToGroup"],
114
+ "severity": "HIGH",
115
+ "description": "Add self to a group with admin or elevated permissions",
116
+ "mitre": "T1548",
117
+ },
118
+ {
119
+ "name": "PassRole+LambdaCreate",
120
+ "permissions": ["iam:PassRole", "lambda:CreateFunction", "lambda:InvokeFunction"],
121
+ "severity": "CRITICAL",
122
+ "description": "Create Lambda with admin role → invoke to execute admin actions",
123
+ "mitre": "T1134.001",
124
+ },
125
+ {
126
+ "name": "PassRole+EC2",
127
+ "permissions": ["iam:PassRole", "ec2:RunInstances"],
128
+ "severity": "HIGH",
129
+ "description": "Launch EC2 with admin instance profile → access IMDS for credentials",
130
+ "mitre": "T1134.001",
131
+ },
132
+ {
133
+ "name": "PassRole+CloudFormation",
134
+ "permissions": ["iam:PassRole", "cloudformation:CreateStack"],
135
+ "severity": "CRITICAL",
136
+ "description": "Create CloudFormation stack with admin role → execute arbitrary actions",
137
+ "mitre": "T1134.001",
138
+ },
139
+ {
140
+ "name": "UpdateAssumeRolePolicy",
141
+ "permissions": ["iam:UpdateAssumeRolePolicy"],
142
+ "severity": "CRITICAL",
143
+ "description": "Update role trust policy to allow self to assume admin role",
144
+ "mitre": "T1548",
145
+ },
146
+ {
147
+ "name": "AssumeRole (direct)",
148
+ "permissions": ["sts:AssumeRole"],
149
+ "severity": "HIGH",
150
+ "description": "Assume a role with higher privileges than current identity",
151
+ "mitre": "T1134.001",
152
+ },
153
+ ]
154
+
155
+
156
+ class IamPrivilegeEscalation(BaseModule):
157
+
158
+ NAME = "iam_privilege_escalation"
159
+ DESCRIPTION = "Enumerate IAM permissions and detect privilege escalation paths using Rhino Security Labs methodology"
160
+ AUTHOR = "ExploitGraph Team"
161
+ VERSION = "1.0.0"
162
+ CATEGORY = "cloud"
163
+ SEVERITY = "CRITICAL"
164
+ MITRE = ["T1078.004", "T1548", "T1134.001"]
165
+ AWS_PARALLEL = "Pacu: iam__privesc_scan | PMapper: pmapper graph"
166
+
167
+ OPTIONS = {
168
+ "AWS_ACCESS_KEY": {"default": "", "required": False, "description": "AWS Access Key (auto-populated from secrets)"},
169
+ "AWS_SECRET_KEY": {"default": "", "required": False, "description": "AWS Secret Key"},
170
+ "AWS_SESSION_TOKEN":{"default": "", "required": False, "description": "AWS Session Token"},
171
+ "AWS_REGION": {"default": "us-east-1", "required": False, "description": "AWS region"},
172
+ "AWS_PROFILE": {"default": "", "required": False, "description": "AWS CLI profile"},
173
+ "SCAN_ROLES": {"default": "true", "required": False, "description": "Also scan assumable roles"},
174
+ }
175
+
176
+ def run(self, session: "Session") -> ModuleResult:
177
+ from core.logger import log
178
+ from core.aws_client import is_available, get_client, _creds_from_session
179
+
180
+ self._timer_start()
181
+ log.section("IAM Privilege Escalation Scanner")
182
+ log.info("MITRE: T1548 — Abuse Elevation Control Mechanism")
183
+ log.info("Methodology: Rhino Security Labs IAM Privilege Escalation")
184
+ log.info("Reference: https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/")
185
+
186
+ if not is_available():
187
+ log.warning("boto3 required: pip install boto3")
188
+ return ModuleResult(False, {}, "boto3 required")
189
+
190
+ # Auto-populate credentials from session
191
+ self._auto_populate_creds(session)
192
+
193
+ region = self.get_option("AWS_REGION", "us-east-1")
194
+ ak = self.get_option("AWS_ACCESS_KEY")
195
+ sk = self.get_option("AWS_SECRET_KEY")
196
+ token = self.get_option("AWS_SESSION_TOKEN", "")
197
+
198
+ if not (ak and sk):
199
+ # Try from session
200
+ ak, sk, token = _creds_from_session(session)
201
+
202
+ if not (ak and sk):
203
+ log.warning("No AWS credentials available.")
204
+ log.step("Run aws_credential_validator first, or set AWS_ACCESS_KEY/SECRET_KEY")
205
+ return ModuleResult(True, {"escalation_paths": 0,
206
+ "skipped_reason": "No credentials"})
207
+
208
+ iam = get_client("iam", region=region, access_key=ak,
209
+ secret_key=sk, session_token=token)
210
+ sts = get_client("sts", region=region, access_key=ak,
211
+ secret_key=sk, session_token=token)
212
+
213
+ if not iam:
214
+ return ModuleResult(False, {}, "Failed to create IAM client")
215
+
216
+ # Get current identity
217
+ current_user = ""
218
+ current_arn = ""
219
+ try:
220
+ identity = sts.get_caller_identity()
221
+ current_arn = identity.get("Arn", "")
222
+ current_user = current_arn.split("/")[-1]
223
+ log.info(f"Current identity: {current_arn}")
224
+ except Exception as e:
225
+ pass # error handled upstream
226
+ except Exception as e:
227
+
228
+ # Enumerate current permissions
229
+ pass # error handled upstream
230
+ log.step("Enumerating current IAM permissions...")
231
+ current_permissions = self._get_effective_permissions(iam, current_user)
232
+ log.info(f"Permissions found: {len(current_permissions)}")
233
+
234
+ # Check each escalation path
235
+ escalation_paths = []
236
+ for path in PRIVESC_PATHS:
237
+ required = path["permissions"]
238
+ if self._has_permissions(required, current_permissions):
239
+ escalation_paths.append(path)
240
+ log.critical(f"ESCALATION PATH: {path['name']}")
241
+ log.step(f" Requires: {', '.join(required)}")
242
+ log.step(f" Method: {path['description']}")
243
+
244
+ # Enumerate all users and roles for broader assessment
245
+ users = self._enum_users(iam)
246
+ roles = self._enum_roles(iam)
247
+
248
+ log.info(f"IAM Users: {len(users)} | Roles: {len(roles)}")
249
+
250
+ # Check for wildcard permissions
251
+ wildcard_findings = self._check_wildcards(iam, current_user)
252
+
253
+ # Check assumable roles
254
+ assumable_admin_roles = []
255
+ if self.get_option("SCAN_ROLES", "true").lower() == "true":
256
+ assumable_admin_roles = self._find_assumable_admin_roles(iam, current_arn)
257
+
258
+ # Generate findings
259
+ self._generate_findings(
260
+ escalation_paths, wildcard_findings,
261
+ assumable_admin_roles, users, roles,
262
+ current_arn, session
263
+ )
264
+
265
+ # Update attack graph
266
+ if escalation_paths or wildcard_findings:
267
+ session.add_graph_node(
268
+ "priv_escalation",
269
+ f"Priv-Esc Paths\n{len(escalation_paths)} found",
270
+ "escalation", "CRITICAL",
271
+ f"Methods: {', '.join(p['name'] for p in escalation_paths[:3])}"
272
+ )
273
+ session.add_graph_edge("aws_access", "priv_escalation",
274
+ "privilege escalation", "T1548")
275
+
276
+ elapsed = self._timer_stop()
277
+ log.success(f"IAM priv-esc scan done in {elapsed}s — "
278
+ f"{len(escalation_paths)} paths, {len(wildcard_findings)} wildcard issues")
279
+
280
+ return ModuleResult(True, {
281
+ "escalation_paths": len(escalation_paths),
282
+ "wildcard_findings": len(wildcard_findings),
283
+ "assumable_admin_roles":len(assumable_admin_roles),
284
+ "users_found": len(users),
285
+ "roles_found": len(roles),
286
+ })
287
+
288
+ def _auto_populate_creds(self, session: "Session"):
289
+ from core.aws_client import _creds_from_session
290
+ ak, sk, token = _creds_from_session(session)
291
+ if ak and not self.get_option("AWS_ACCESS_KEY"):
292
+ self.set_option("AWS_ACCESS_KEY", ak)
293
+ if sk and not self.get_option("AWS_SECRET_KEY"):
294
+ self.set_option("AWS_SECRET_KEY", sk)
295
+
296
+ def _get_effective_permissions(self, iam, username: str) -> set[str]:
297
+ """Get all IAM permissions for the current user."""
298
+ from core.logger import log
299
+ permissions = set()
300
+
301
+ if not username:
302
+ return permissions
303
+
304
+ try:
305
+ # Attached managed policies
306
+ resp = iam.list_attached_user_policies(UserName=username)
307
+ for policy in resp.get("AttachedPolicies", []):
308
+ perms = self._expand_policy(iam, policy["PolicyArn"])
309
+ permissions.update(perms)
310
+
311
+ # Inline policies
312
+ resp2 = iam.list_user_policies(UserName=username)
313
+ for policy_name in resp2.get("PolicyNames", []):
314
+ try:
315
+ doc = iam.get_user_policy(UserName=username, PolicyName=policy_name)
316
+ perms = self._extract_permissions_from_doc(
317
+ doc.get("PolicyDocument", {})
318
+ )
319
+ permissions.update(perms)
320
+ except Exception:
321
+ pass # network/connection error — continue scanning
322
+
323
+ # Group policies
324
+ resp3 = iam.list_groups_for_user(UserName=username)
325
+ for group in resp3.get("Groups", []):
326
+ gname = group["GroupName"]
327
+ try:
328
+ attached = iam.list_attached_group_policies(GroupName=gname)
329
+ for p in attached.get("AttachedPolicies", []):
330
+ perms = self._expand_policy(iam, p["PolicyArn"])
331
+ permissions.update(perms)
332
+ except Exception:
333
+ pass # network/connection error — continue scanning
334
+
335
+ except Exception as e:
336
+ pass # error handled upstream
337
+ except Exception as e:
338
+
339
+ pass # error handled upstream
340
+ return permissions
341
+
342
+ def _expand_policy(self, iam, policy_arn: str) -> set[str]:
343
+ """Get all actions from a managed policy's default version."""
344
+ permissions = set()
345
+ try:
346
+ policy = iam.get_policy(PolicyArn=policy_arn)
347
+ version_id = policy["Policy"]["DefaultVersionId"]
348
+ doc = iam.get_policy_version(PolicyArn=policy_arn,
349
+ VersionId=version_id)
350
+ return self._extract_permissions_from_doc(
351
+ doc["PolicyVersion"].get("Document", {})
352
+ )
353
+ except Exception:
354
+ pass # error handled upstream
355
+ except Exception:
356
+
357
+ pass # error handled upstream
358
+ def _extract_permissions_from_doc(self, doc) -> set[str]:
359
+ """Extract action strings from a policy document."""
360
+ permissions = set()
361
+ if isinstance(doc, str):
362
+ try:
363
+ doc = json.loads(doc)
364
+ except Exception:
365
+ pass # error handled upstream
366
+ except Exception:
367
+
368
+ pass # error handled upstream
369
+ for statement in doc.get("Statement", []):
370
+ if statement.get("Effect", "Deny") != "Allow":
371
+ continue
372
+ actions = statement.get("Action", [])
373
+ if isinstance(actions, str):
374
+ actions = [actions]
375
+ for action in actions:
376
+ permissions.add(action.lower())
377
+ # Wildcards: iam:* → all iam actions
378
+ if action == "*":
379
+ permissions.add("*")
380
+ return permissions
381
+
382
+ def _has_permissions(self, required: list[str], current: set[str]) -> bool:
383
+ """Check if all required permissions are in current permission set."""
384
+ if "*" in current:
385
+ return True
386
+ for perm in required:
387
+ service, action = perm.split(":", 1) if ":" in perm else (perm, "*")
388
+ service_wildcard = f"{service}:*"
389
+ if (perm.lower() in current or
390
+ service_wildcard.lower() in current or
391
+ "*" in current):
392
+ continue
393
+ return False
394
+ return bool(required) # All required perms satisfied
395
+
396
+ def _enum_users(self, iam) -> list[dict]:
397
+ from core.logger import log
398
+ try:
399
+ resp = iam.list_users(MaxItems=50)
400
+ users = resp.get("Users", [])
401
+ for u in users[:10]:
402
+ log.step(f"User: {u['UserName']} (created: {str(u.get('CreateDate',''))[:10]})")
403
+ return users
404
+ except Exception:
405
+ pass # error handled upstream
406
+ except Exception:
407
+
408
+ pass # error handled upstream
409
+ def _enum_roles(self, iam) -> list[dict]:
410
+ from core.logger import log
411
+ try:
412
+ resp = iam.list_roles(MaxItems=50)
413
+ roles = resp.get("Roles", [])
414
+ for r in roles[:10]:
415
+ log.step(f"Role: {r['RoleName']}")
416
+ return roles
417
+ except Exception:
418
+ pass # error handled upstream
419
+ except Exception:
420
+
421
+ pass # error handled upstream
422
+ def _check_wildcards(self, iam, username: str) -> list[dict]:
423
+ from core.logger import log
424
+ wildcards = []
425
+ if not username:
426
+ return wildcards
427
+ permissions = self._get_effective_permissions(iam, username)
428
+ if "*" in permissions:
429
+ wildcards.append({"type": "full_admin", "permission": "*"})
430
+ log.critical("WILDCARD (*) PERMISSION FOUND — Full admin access!")
431
+ for perm in permissions:
432
+ if perm.endswith(":*"):
433
+ wildcards.append({"type": "service_wildcard", "permission": perm})
434
+ log.warning(f"Service wildcard: {perm}")
435
+ return wildcards
436
+
437
+ def _find_assumable_admin_roles(self, iam, caller_arn: str) -> list[str]:
438
+ from core.logger import log
439
+ admin_roles = []
440
+ try:
441
+ resp = iam.list_roles(MaxItems=100)
442
+ for role in resp.get("Roles", []):
443
+ trust = json.dumps(role.get("AssumeRolePolicyDocument", {}))
444
+ # Check if our identity can assume this role
445
+ if (caller_arn in trust or
446
+ '"*"' in trust or
447
+ "sts:AssumeRole" in trust):
448
+ role_name = role["RoleName"]
449
+ # Check if it has admin policies
450
+ try:
451
+ attached = iam.list_attached_role_policies(RoleName=role_name)
452
+ for p in attached.get("AttachedPolicies", []):
453
+ if any(ind in p["PolicyName"] for ind in
454
+ ["Administrator", "FullAccess", "PowerUser"]):
455
+ admin_roles.append(role["Arn"])
456
+ log.critical(f"Assumable admin role: {role['Arn']}")
457
+ except Exception:
458
+ pass # network/connection error — continue scanning
459
+ except Exception:
460
+ pass # network/connection error — continue scanning
461
+ return admin_roles
462
+
463
+ def _generate_findings(self, escalation_paths, wildcard_findings,
464
+ assumable_admin_roles, users, roles,
465
+ current_arn, session: "Session"):
466
+ from core.logger import log
467
+
468
+ for path in escalation_paths:
469
+ session.add_finding(
470
+ module = self.NAME,
471
+ title = f"IAM Privilege Escalation Path: {path['name']}",
472
+ severity = path["severity"],
473
+ description = (
474
+ f"Current identity ({current_arn.split('/')[-1]}) has permissions "
475
+ f"to escalate privileges via: {path['description']}"
476
+ ),
477
+ evidence = (
478
+ f"Method: {path['name']}\n"
479
+ f"Required permissions: {', '.join(path['permissions'])}\n"
480
+ f"Current identity: {current_arn}"
481
+ ),
482
+ recommendation = (
483
+ f"Remove or restrict permission(s): {', '.join(path['permissions'])}. "
484
+ "Apply least-privilege IAM policies. Use IAM Access Analyzer."
485
+ ),
486
+ cvss_score = 9.5 if path["severity"] == "CRITICAL" else 7.5,
487
+ aws_parallel = f"Pacu: iam__privesc_scan would detect: {path['name']}",
488
+ mitre_technique = path["mitre"],
489
+ )
490
+
491
+ for wc in wildcard_findings:
492
+ session.add_finding(
493
+ module = self.NAME,
494
+ title = f"Dangerous Wildcard IAM Permission: {wc['permission']}",
495
+ severity = "CRITICAL",
496
+ description = f"Identity has wildcard permission '{wc['permission']}' which grants unrestricted access.",
497
+ evidence = f"Permission: {wc['permission']}\nIdentity: {current_arn}",
498
+ recommendation = "Replace wildcard with specific required actions. Run aws iam generate-service-last-accessed-details.",
499
+ cvss_score = 10.0,
500
+ aws_parallel = "IAM policy with Action: '*' — violates least privilege",
501
+ mitre_technique = "T1548",
502
+ )
503
+
504
+ for role_arn in assumable_admin_roles:
505
+ session.add_finding(
506
+ module = self.NAME,
507
+ title = f"Assumable Admin Role: {role_arn.split('/')[-1]}",
508
+ severity = "CRITICAL",
509
+ description = f"Current identity can assume admin role: {role_arn}",
510
+ evidence = f"Role ARN: {role_arn}\nCurrent: {current_arn}",
511
+ recommendation = "Restrict role trust policy. Add conditions (MFA, source IP).",
512
+ cvss_score = 9.8,
513
+ aws_parallel = "sts:AssumeRole to admin role — immediate full access",
514
+ mitre_technique = "T1134.001",
515
+ )