exploitgraph 1.0.0__tar.gz → 1.0.2__tar.gz

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 (54) hide show
  1. {exploitgraph-1.0.0/exploitgraph.egg-info → exploitgraph-1.0.2}/PKG-INFO +1 -1
  2. exploitgraph-1.0.2/exploitgraph/data/wordlists/backup_files.txt +23 -0
  3. exploitgraph-1.0.2/exploitgraph/data/wordlists/common_paths.txt +149 -0
  4. exploitgraph-1.0.2/exploitgraph/data/wordlists/s3_buckets.txt +38 -0
  5. exploitgraph-1.0.2/exploitgraph/data/wordlists/subdomains.txt +31 -0
  6. {exploitgraph-1.0.0 → exploitgraph-1.0.2/exploitgraph.egg-info}/PKG-INFO +1 -1
  7. exploitgraph-1.0.2/exploitgraph.egg-info/SOURCES.txt +14 -0
  8. exploitgraph-1.0.2/exploitgraph.egg-info/top_level.txt +1 -0
  9. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/pyproject.toml +3 -4
  10. exploitgraph-1.0.0/core/__init__.py +0 -0
  11. exploitgraph-1.0.0/core/attack_graph.py +0 -83
  12. exploitgraph-1.0.0/core/aws_client.py +0 -284
  13. exploitgraph-1.0.0/core/config.py +0 -83
  14. exploitgraph-1.0.0/core/console.py +0 -469
  15. exploitgraph-1.0.0/core/context_engine.py +0 -172
  16. exploitgraph-1.0.0/core/correlator.py +0 -476
  17. exploitgraph-1.0.0/core/http_client.py +0 -243
  18. exploitgraph-1.0.0/core/logger.py +0 -97
  19. exploitgraph-1.0.0/core/module_loader.py +0 -69
  20. exploitgraph-1.0.0/core/risk_engine.py +0 -47
  21. exploitgraph-1.0.0/core/session_manager.py +0 -254
  22. exploitgraph-1.0.0/exploitgraph.egg-info/SOURCES.txt +0 -46
  23. exploitgraph-1.0.0/exploitgraph.egg-info/top_level.txt +0 -2
  24. exploitgraph-1.0.0/modules/__init__.py +0 -0
  25. exploitgraph-1.0.0/modules/base.py +0 -82
  26. exploitgraph-1.0.0/modules/cloud/__init__.py +0 -0
  27. exploitgraph-1.0.0/modules/cloud/aws_credential_validator.py +0 -340
  28. exploitgraph-1.0.0/modules/cloud/azure_enum.py +0 -289
  29. exploitgraph-1.0.0/modules/cloud/cloudtrail_analyzer.py +0 -494
  30. exploitgraph-1.0.0/modules/cloud/gcp_enum.py +0 -272
  31. exploitgraph-1.0.0/modules/cloud/iam_enum.py +0 -321
  32. exploitgraph-1.0.0/modules/cloud/iam_privilege_escalation.py +0 -515
  33. exploitgraph-1.0.0/modules/cloud/metadata_check.py +0 -315
  34. exploitgraph-1.0.0/modules/cloud/s3_enum.py +0 -469
  35. exploitgraph-1.0.0/modules/discovery/__init__.py +0 -0
  36. exploitgraph-1.0.0/modules/discovery/http_enum.py +0 -235
  37. exploitgraph-1.0.0/modules/discovery/subdomain_enum.py +0 -260
  38. exploitgraph-1.0.0/modules/exploitation/__init__.py +0 -0
  39. exploitgraph-1.0.0/modules/exploitation/api_exploit.py +0 -403
  40. exploitgraph-1.0.0/modules/exploitation/jwt_attack.py +0 -346
  41. exploitgraph-1.0.0/modules/exploitation/ssrf_scanner.py +0 -258
  42. exploitgraph-1.0.0/modules/reporting/__init__.py +0 -0
  43. exploitgraph-1.0.0/modules/reporting/html_report.py +0 -446
  44. exploitgraph-1.0.0/modules/reporting/json_export.py +0 -107
  45. exploitgraph-1.0.0/modules/secrets/__init__.py +0 -0
  46. exploitgraph-1.0.0/modules/secrets/file_secrets.py +0 -358
  47. exploitgraph-1.0.0/modules/secrets/git_secrets.py +0 -267
  48. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/LICENSE +0 -0
  49. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/README.md +0 -0
  50. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/exploitgraph.egg-info/dependency_links.txt +0 -0
  51. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/exploitgraph.egg-info/entry_points.txt +0 -0
  52. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/exploitgraph.egg-info/requires.txt +0 -0
  53. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/setup.cfg +0 -0
  54. {exploitgraph-1.0.0 → exploitgraph-1.0.2}/tests/test_exploitgraph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exploitgraph
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Automated attack path discovery and exploitation framework for cloud-native applications
5
5
  Author-email: Prajwal Pawar <prajwal@exploitgraph.io>
6
6
  License: MIT
@@ -0,0 +1,23 @@
1
+ # ExploitGraph - Backup File Paths
2
+ /static/backups/
3
+ /backups/
4
+ /backup/
5
+ /.env
6
+ /.env.backup
7
+ /.env.production
8
+ /.env.local
9
+ /config.json
10
+ /config.yaml
11
+ /app.yaml
12
+ /database.yml
13
+ /Dockerfile
14
+ /docker-compose.yml
15
+ /docker-compose.yaml
16
+ /package.json
17
+ /.git/config
18
+ /wp-config.php
19
+ /web.config
20
+ /settings.py
21
+ /settings.php
22
+ /configuration.php
23
+ /config.php
@@ -0,0 +1,149 @@
1
+ # ExploitGraph - Common HTTP Paths Wordlist
2
+ # Generic paths for endpoint enumeration against any web application
3
+ # Format: one path per line, lines starting with # are comments
4
+
5
+ # Root and API bases
6
+ /
7
+ /api
8
+ /api/v1
9
+ /api/v2
10
+ /api/v3
11
+ /v1
12
+ /v2
13
+
14
+ # Documentation
15
+ /docs
16
+ /api/docs
17
+ /swagger
18
+ /swagger.json
19
+ /swagger-ui.html
20
+ /swagger-ui/
21
+ /openapi.json
22
+ /openapi.yaml
23
+ /redoc
24
+ /api-docs
25
+ /api/swagger
26
+ /apidocs
27
+
28
+ # Authentication
29
+ /login
30
+ /auth
31
+ /auth/login
32
+ /api/auth/login
33
+ /api/login
34
+ /api/auth
35
+ /oauth
36
+ /oauth/token
37
+ /token
38
+ /api/token
39
+ /signin
40
+ /api/signin
41
+
42
+ # User / Account
43
+ /api/users
44
+ /api/user
45
+ /api/me
46
+ /api/account
47
+ /api/profile
48
+ /users
49
+ /account
50
+
51
+ # Admin
52
+ /admin
53
+ /admin/
54
+ /api/admin
55
+ /api/admin/users
56
+ /api/admin/config
57
+ /dashboard
58
+ /console
59
+ /management
60
+ /api/management
61
+ /panel
62
+ /control
63
+ /backend
64
+
65
+ # Health / Status
66
+ /health
67
+ /api/health
68
+ /status
69
+ /ping
70
+ /api/status
71
+ /api/ping
72
+ /metrics
73
+ /api/metrics
74
+ /actuator
75
+ /actuator/health
76
+ /actuator/env
77
+ /actuator/beans
78
+ /actuator/mappings
79
+ /actuator/info
80
+
81
+ # Debug / Config (often left enabled accidentally)
82
+ /debug
83
+ /api/debug
84
+ /api/debug/config
85
+ /config
86
+ /api/config
87
+ /settings
88
+ /api/settings
89
+ /env
90
+ /api/env
91
+ /__debug__
92
+ /api/internal
93
+
94
+ # Cloud Storage
95
+ /static/
96
+ /static/backups/
97
+ /backups/
98
+ /backup/
99
+ /uploads/
100
+ /files/
101
+ /storage/
102
+ /assets/
103
+ /media/
104
+
105
+ # Exposed files
106
+ /.env
107
+ /.env.production
108
+ /.env.local
109
+ /.env.backup
110
+ /.env.example
111
+ /config.json
112
+ /config.yaml
113
+ /config.yml
114
+ /app.yaml
115
+ /app.json
116
+ /settings.json
117
+ /secrets.json
118
+
119
+ # Git exposure
120
+ /.git/config
121
+ /.git/HEAD
122
+ /.git/COMMIT_EDITMSG
123
+
124
+ # Common backup filenames
125
+ /backup.zip
126
+ /backup.tar.gz
127
+ /backup.sql
128
+ /dump.sql
129
+ /db.sql
130
+ /database.sql
131
+ /site.zip
132
+ /app.zip
133
+ /source.zip
134
+
135
+ # Web server files
136
+ /robots.txt
137
+ /sitemap.xml
138
+ /.htaccess
139
+ /.htpasswd
140
+ /web.config
141
+ /phpinfo.php
142
+ /server-status
143
+ /server-info
144
+ /info.php
145
+
146
+ # AWS / Cloud specific
147
+ /latest/meta-data/
148
+ /latest/user-data
149
+ /?list-type=2
@@ -0,0 +1,38 @@
1
+ # ExploitGraph - S3 Bucket Name Wordlist
2
+ # Common bucket naming patterns used by organizations
3
+ backups
4
+ backup
5
+ assets
6
+ static
7
+ uploads
8
+ files
9
+ data
10
+ logs
11
+ config
12
+ configs
13
+ media
14
+ images
15
+ public
16
+ private
17
+ dev
18
+ staging
19
+ prod
20
+ production
21
+ test
22
+ demo
23
+ archive
24
+ exports
25
+ reports
26
+ documents
27
+ docs
28
+ storage
29
+ cdn
30
+ web
31
+ app
32
+ api
33
+ secrets
34
+ credentials
35
+ keys
36
+ database
37
+ db
38
+ dump
@@ -0,0 +1,31 @@
1
+ # ExploitGraph - Common Subdomain Wordlist
2
+ api
3
+ dev
4
+ staging
5
+ test
6
+ admin
7
+ dashboard
8
+ portal
9
+ app
10
+ mobile
11
+ secure
12
+ vpn
13
+ mail
14
+ smtp
15
+ ftp
16
+ cdn
17
+ assets
18
+ static
19
+ backup
20
+ files
21
+ upload
22
+ docs
23
+ wiki
24
+ support
25
+ help
26
+ status
27
+ monitor
28
+ logs
29
+ metrics
30
+ internal
31
+ corp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exploitgraph
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Automated attack path discovery and exploitation framework for cloud-native applications
5
5
  Author-email: Prajwal Pawar <prajwal@exploitgraph.io>
6
6
  License: MIT
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ exploitgraph.egg-info/PKG-INFO
5
+ exploitgraph.egg-info/SOURCES.txt
6
+ exploitgraph.egg-info/dependency_links.txt
7
+ exploitgraph.egg-info/entry_points.txt
8
+ exploitgraph.egg-info/requires.txt
9
+ exploitgraph.egg-info/top_level.txt
10
+ exploitgraph/data/wordlists/backup_files.txt
11
+ exploitgraph/data/wordlists/common_paths.txt
12
+ exploitgraph/data/wordlists/s3_buckets.txt
13
+ exploitgraph/data/wordlists/subdomains.txt
14
+ tests/test_exploitgraph.py
@@ -0,0 +1 @@
1
+ exploitgraph
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "exploitgraph"
7
- version = "1.0.0"
7
+ version = "1.0.2"
8
8
  description = "Automated attack path discovery and exploitation framework for cloud-native applications"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -50,9 +50,8 @@ Repository = "https://github.com/prajwalpawar/ExploitGraph"
50
50
  "Bug Tracker" = "https://github.com/prajwalpawar/ExploitGraph/issues"
51
51
  Documentation = "https://github.com/prajwalpawar/ExploitGraph/wiki"
52
52
 
53
- [tool.setuptools.packages.find]
54
- where = ["."]
55
- include = ["exploitgraph*", "core*", "modules*"]
53
+ [tool.setuptools]
54
+ packages = ["exploitgraph"]
56
55
 
57
56
  [tool.setuptools.package-data]
58
57
  "*" = ["data/wordlists/*.txt", "data/templates/*.j2", "*.yaml", "*.yml"]
File without changes
@@ -1,83 +0,0 @@
1
- """ExploitGraph - Attack path graph engine (networkx)."""
2
- from __future__ import annotations
3
- import json
4
- from typing import TYPE_CHECKING
5
- import networkx as nx
6
- if TYPE_CHECKING:
7
- from core.session_manager import Session
8
-
9
- SEVERITY_COLOR = {"CRITICAL":"#dc2626","HIGH":"#ea580c","MEDIUM":"#d97706",
10
- "LOW":"#16a34a","INFO":"#2563eb"}
11
- MITRE_REF = {
12
- "T1595":"Active Scanning", "T1595.003":"Wordlist Scanning",
13
- "T1580":"Cloud Infrastructure Discovery", "T1530":"Data from Cloud Storage",
14
- "T1552.001":"Credentials in Files", "T1552.004":"Private Keys",
15
- "T1552.005":"Cloud Instance Metadata API", "T1078":"Valid Accounts",
16
- "T1078.004":"Valid Accounts: Cloud Accounts", "T1548":"Abuse Elevation Control",
17
- "T1550.001":"Application Access Token", "T1069.003":"Cloud Groups",
18
- "T1110.001":"Password Guessing",
19
- }
20
-
21
-
22
- class AttackGraph:
23
- def __init__(self): self.G: nx.DiGraph = nx.DiGraph()
24
-
25
- def build(self, session: "Session"):
26
- self.G.clear()
27
- for n in session.graph_nodes:
28
- self.G.add_node(n["node_id"], label=n.get("label",""),
29
- type=n.get("node_type","asset"),
30
- severity=n.get("severity","INFO"),
31
- details=n.get("details",""),
32
- color=SEVERITY_COLOR.get(n.get("severity","INFO"),"#6b7280"))
33
- for e in session.graph_edges:
34
- if e["source"] in self.G and e["target"] in self.G:
35
- self.G.add_edge(e["source"], e["target"],
36
- label=e.get("label",""), technique=e.get("technique",""))
37
-
38
- def find_paths(self, src="attacker", tgt="compromise") -> list[list[str]]:
39
- try:
40
- nodes = list(self.G.nodes())
41
- src = next((n for n in nodes if src in n), src)
42
- tgt = next((n for n in nodes if tgt in n.lower()), tgt)
43
- return list(nx.all_simple_paths(self.G, src, tgt))
44
- except Exception:
45
- return []
46
-
47
- def stats(self) -> dict:
48
- techs = set()
49
- for _,_,d in self.G.edges(data=True):
50
- if d.get("technique"): techs.add(d["technique"])
51
- critical = [n for n,d in self.G.nodes(data=True) if d.get("severity") in ("CRITICAL","HIGH")]
52
- return dict(nodes=self.G.number_of_nodes(), edges=self.G.number_of_edges(),
53
- critical_nodes=len(critical), mitre_techniques=sorted(techs),
54
- is_connected=nx.is_weakly_connected(self.G) if self.G.nodes() else False)
55
-
56
- def to_json(self) -> dict:
57
- nodes = [dict(id=nid, label=d.get("label",nid), type=d.get("type","asset"),
58
- severity=d.get("severity","INFO"), details=d.get("details",""),
59
- color=d.get("color","#6b7280"))
60
- for nid,d in self.G.nodes(data=True)]
61
- edges = [dict(source=s, target=t, label=d.get("label",""), technique=d.get("technique",""))
62
- for s,t,d in self.G.edges(data=True)]
63
- return dict(nodes=nodes, edges=edges, stats=self.stats(), attack_paths=self.find_paths())
64
-
65
- def print_ascii(self) -> str:
66
- paths = self.find_paths()
67
- if not paths: return "No complete attack path found."
68
- path = max(paths, key=len)
69
- lines = []
70
- colors = {"CRITICAL":"\033[91m","HIGH":"\033[93m","MEDIUM":"\033[96m","INFO":"\033[97m"}
71
- for i, nid in enumerate(path):
72
- d = self.G.nodes.get(nid, {})
73
- label = d.get("label", nid).split("\n")[0]
74
- sev = d.get("severity","INFO")
75
- if i > 0:
76
- ed = self.G.edges.get((path[i-1], nid), {})
77
- tech = ed.get("technique","")
78
- lines.append(f" ↓ {tech}")
79
- lines.append(f"{colors.get(sev,'')} [{sev}] {label}\033[0m")
80
- return "\n".join(lines)
81
-
82
-
83
- attack_graph = AttackGraph()
@@ -1,284 +0,0 @@
1
- """
2
- ExploitGraph - AWS Client Factory
3
- Production-grade boto3 session management with:
4
- - Automatic retry with exponential backoff
5
- - Region fallback (us-east-1 → us-west-2 → eu-west-1)
6
- - Credential injection from session secrets
7
- - Standardised response parsing
8
- - GuardDuty detection-awareness tagging
9
-
10
- All modules call get_client() — no per-module boto3 setup needed.
11
- """
12
- from __future__ import annotations
13
- import time
14
- import functools
15
- from typing import TYPE_CHECKING, Any, Optional
16
-
17
- if TYPE_CHECKING:
18
- from core.session_manager import Session
19
-
20
- try:
21
- import boto3
22
- import botocore.config
23
- import botocore.exceptions
24
- HAS_BOTO3 = True
25
- except ImportError:
26
- HAS_BOTO3 = False
27
-
28
- DEFAULT_REGION = "us-east-1"
29
- FALLBACK_REGIONS = ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]
30
-
31
- # Retry config: 3 attempts, exponential backoff
32
- _RETRY_CONFIG = None
33
- if HAS_BOTO3:
34
- _RETRY_CONFIG = botocore.config.Config(
35
- retries={"max_attempts": 3, "mode": "adaptive"},
36
- connect_timeout=10,
37
- read_timeout=20,
38
- )
39
-
40
- # GuardDuty-awareness: these API calls are known to trigger alerts
41
- GUARDDUTY_NOISY_APIS = {
42
- "GetCallerIdentity": "HIGH", # Recon — always logged
43
- "GetAccountAuthorizationDetails": "CRITICAL", # Full IAM dump — very noisy
44
- "ListUsers": "MEDIUM",
45
- "ListRoles": "MEDIUM",
46
- "CreateAccessKey": "HIGH", # Backdoor credential
47
- "DeleteTrail": "CRITICAL", # Covering tracks
48
- "StopLogging": "CRITICAL",
49
- "PutBucketLogging": "HIGH",
50
- "DescribeInstances": "LOW", # Normal ops but logged
51
- }
52
-
53
- GUARDDUTY_STEALTHY_APIS = {
54
- "ListBuckets": "no-sign-request avoids auth logs",
55
- "GetObject": "anonymous S3 access not in CloudTrail",
56
- "GetBucketAcl": "no-sign-request avoids auth logs",
57
- }
58
-
59
-
60
- def get_client(service: str,
61
- session: "Optional[Session]" = None,
62
- region: str = DEFAULT_REGION,
63
- profile: str = "",
64
- access_key: str = "",
65
- secret_key: str = "",
66
- session_token: str = "") -> Any:
67
- """
68
- Return a boto3 client. Credential resolution order:
69
- 1. Explicit access_key/secret_key args
70
- 2. Session secrets (injected by file_secrets / cloudtrail_analyzer)
71
- 3. AWS CLI profile
72
- 4. Default boto3 chain (env vars → ~/.aws → IMDS)
73
-
74
- Includes automatic retry + exponential backoff.
75
- Returns None if boto3 unavailable or all credential sources fail.
76
- """
77
- if not HAS_BOTO3:
78
- return None
79
-
80
- if session and not access_key:
81
- access_key, secret_key, session_token = _creds_from_session(session)
82
-
83
- return _build_client(service, region, profile,
84
- access_key, secret_key, session_token)
85
-
86
-
87
- def _build_client(service: str, region: str, profile: str,
88
- access_key: str, secret_key: str,
89
- session_token: str) -> Any:
90
- """Build boto3 client with retry config and region fallback."""
91
- regions_to_try = [region] if region else FALLBACK_REGIONS
92
-
93
- for reg in regions_to_try:
94
- try:
95
- if access_key and secret_key:
96
- client = boto3.client(
97
- service,
98
- region_name=reg,
99
- aws_access_key_id=access_key,
100
- aws_secret_access_key=secret_key,
101
- aws_session_token=session_token or None,
102
- config=_RETRY_CONFIG,
103
- )
104
- elif profile:
105
- boto_session = boto3.Session(profile_name=profile, region_name=reg)
106
- client = boto_session.client(service, config=_RETRY_CONFIG)
107
- else:
108
- client = boto3.client(service, region_name=reg, config=_RETRY_CONFIG)
109
- return client
110
- except Exception:
111
- continue
112
-
113
- return None
114
-
115
-
116
- def get_session(session: "Optional[Session]" = None,
117
- region: str = DEFAULT_REGION,
118
- profile: str = "") -> Any:
119
- """Return a boto3 Session for multi-service use."""
120
- if not HAS_BOTO3:
121
- return None
122
-
123
- access_key, secret_key, session_token = "", "", ""
124
- if session:
125
- access_key, secret_key, session_token = _creds_from_session(session)
126
-
127
- try:
128
- if access_key and secret_key:
129
- return boto3.Session(
130
- region_name=region,
131
- aws_access_key_id=access_key,
132
- aws_secret_access_key=secret_key,
133
- aws_session_token=session_token or None,
134
- )
135
- elif profile:
136
- return boto3.Session(profile_name=profile, region_name=region)
137
- else:
138
- return boto3.Session(region_name=region)
139
- except Exception:
140
- return None
141
-
142
-
143
- def safe_call(client: Any, method: str, **kwargs) -> tuple[bool, Any, str]:
144
- """
145
- Safely call a boto3 client method with retry + error handling.
146
-
147
- Returns: (success, response_or_None, error_message)
148
-
149
- Usage:
150
- ok, resp, err = safe_call(iam, 'list_users', MaxItems=5)
151
- if ok:
152
- users = resp['Users']
153
- """
154
- if client is None:
155
- return False, None, "boto3 client is None"
156
-
157
- max_attempts = 3
158
- for attempt in range(max_attempts):
159
- try:
160
- fn = getattr(client, method)
161
- response = fn(**kwargs)
162
- return True, response, ""
163
- except botocore.exceptions.ClientError as e:
164
- code = e.response["Error"]["Code"]
165
- msg = e.response["Error"]["Message"]
166
- # Don't retry auth errors
167
- if code in ("InvalidClientTokenId", "AuthFailure",
168
- "UnauthorizedAccess", "AccessDenied",
169
- "ExpiredTokenException"):
170
- return False, None, f"{code}: {msg}"
171
- # Retry throttle errors
172
- if code in ("Throttling", "RequestLimitExceeded",
173
- "TooManyRequestsException"):
174
- if attempt < max_attempts - 1:
175
- time.sleep(2 ** attempt)
176
- continue
177
- return False, None, f"{code}: {msg}"
178
- except Exception as e:
179
- if attempt < max_attempts - 1:
180
- time.sleep(1)
181
- continue
182
- return False, None, str(e)
183
-
184
- return False, None, "Max retry attempts exceeded"
185
-
186
-
187
- def verify_credentials(access_key: str, secret_key: str,
188
- session_token: str = "",
189
- region: str = DEFAULT_REGION) -> dict:
190
- """
191
- Verify AWS credentials via STS:GetCallerIdentity.
192
- Returns structured result — never raises.
193
-
194
- NOTE: GetCallerIdentity IS logged in CloudTrail and flagged by GuardDuty.
195
- Severity: HIGH (known recon indicator).
196
- """
197
- if not HAS_BOTO3:
198
- return {"valid": False, "error": "boto3 not installed",
199
- "guardduty_risk": "N/A"}
200
-
201
- result = {
202
- "valid": False,
203
- "arn": "",
204
- "account": "",
205
- "user_id": "",
206
- "username": "",
207
- "error": "",
208
- "guardduty_risk": GUARDDUTY_NOISY_APIS.get("GetCallerIdentity", "HIGH"),
209
- "guardduty_note": "GetCallerIdentity is always logged and a known recon indicator",
210
- }
211
-
212
- client = _build_client("sts", region, "", access_key, secret_key, session_token)
213
- if not client:
214
- result["error"] = "Could not create STS client"
215
- return result
216
-
217
- ok, resp, err = safe_call(client, "get_caller_identity")
218
- if ok:
219
- result["valid"] = True
220
- result["arn"] = resp.get("Arn", "")
221
- result["account"] = resp.get("Account", "")
222
- result["user_id"] = resp.get("UserId", "")
223
- # Extract username from ARN
224
- arn = result["arn"]
225
- if "/" in arn:
226
- result["username"] = arn.split("/")[-1]
227
- else:
228
- result["error"] = err
229
-
230
- return result
231
-
232
-
233
- def detect_region(access_key: str, secret_key: str,
234
- session_token: str = "") -> str:
235
- """
236
- Detect the primary region for these credentials.
237
- Falls back to us-east-1 if detection fails.
238
- """
239
- if not HAS_BOTO3:
240
- return DEFAULT_REGION
241
-
242
- for region in FALLBACK_REGIONS:
243
- client = _build_client("sts", region, "", access_key, secret_key, session_token)
244
- ok, resp, _ = safe_call(client, "get_caller_identity")
245
- if ok:
246
- return region
247
-
248
- return DEFAULT_REGION
249
-
250
-
251
- def _creds_from_session(session: "Session") -> tuple[str, str, str]:
252
- """Extract best available AWS credentials from session secrets."""
253
- access_key = secret_key = session_token = ""
254
- for s in session.secrets:
255
- t = s.get("secret_type", "")
256
- v = s.get("value", "")
257
- if t == "AWS_ACCESS_KEY" and not access_key:
258
- access_key = v
259
- elif t == "AWS_SECRET_KEY" and not secret_key:
260
- secret_key = v
261
- elif t == "AWS_SESSION_TOKEN" and not session_token:
262
- session_token = v
263
- return access_key, secret_key, session_token
264
-
265
-
266
- def guardduty_risk(api_name: str) -> tuple[str, str]:
267
- """
268
- Return (risk_level, explanation) for a given API call.
269
- Used by modules to annotate findings with detection awareness.
270
- """
271
- if api_name in GUARDDUTY_NOISY_APIS:
272
- return GUARDDUTY_NOISY_APIS[api_name], "GuardDuty: known high-signal event"
273
- if api_name in GUARDDUTY_STEALTHY_APIS:
274
- return "LOW", GUARDDUTY_STEALTHY_APIS[api_name]
275
- return "LOW", "Not a known GuardDuty trigger"
276
-
277
-
278
- def is_available() -> bool:
279
- return HAS_BOTO3
280
-
281
-
282
- def has_credentials(session: "Session") -> bool:
283
- ak, sk, _ = _creds_from_session(session)
284
- return bool(ak and sk)