exploitgraph 1.0.1__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.1 → exploitgraph-1.0.2}/PKG-INFO +1 -1
  2. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph.egg-info/PKG-INFO +1 -1
  3. exploitgraph-1.0.2/exploitgraph.egg-info/SOURCES.txt +14 -0
  4. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/pyproject.toml +3 -4
  5. exploitgraph-1.0.1/exploitgraph/__init__.py +0 -99
  6. exploitgraph-1.0.1/exploitgraph/core/__init__.py +0 -0
  7. exploitgraph-1.0.1/exploitgraph/core/attack_graph.py +0 -83
  8. exploitgraph-1.0.1/exploitgraph/core/aws_client.py +0 -284
  9. exploitgraph-1.0.1/exploitgraph/core/config.py +0 -83
  10. exploitgraph-1.0.1/exploitgraph/core/console.py +0 -469
  11. exploitgraph-1.0.1/exploitgraph/core/context_engine.py +0 -172
  12. exploitgraph-1.0.1/exploitgraph/core/correlator.py +0 -476
  13. exploitgraph-1.0.1/exploitgraph/core/http_client.py +0 -243
  14. exploitgraph-1.0.1/exploitgraph/core/logger.py +0 -97
  15. exploitgraph-1.0.1/exploitgraph/core/module_loader.py +0 -69
  16. exploitgraph-1.0.1/exploitgraph/core/risk_engine.py +0 -47
  17. exploitgraph-1.0.1/exploitgraph/core/session_manager.py +0 -254
  18. exploitgraph-1.0.1/exploitgraph/modules/__init__.py +0 -0
  19. exploitgraph-1.0.1/exploitgraph/modules/base.py +0 -82
  20. exploitgraph-1.0.1/exploitgraph/modules/cloud/__init__.py +0 -0
  21. exploitgraph-1.0.1/exploitgraph/modules/cloud/aws_credential_validator.py +0 -340
  22. exploitgraph-1.0.1/exploitgraph/modules/cloud/azure_enum.py +0 -289
  23. exploitgraph-1.0.1/exploitgraph/modules/cloud/cloudtrail_analyzer.py +0 -494
  24. exploitgraph-1.0.1/exploitgraph/modules/cloud/gcp_enum.py +0 -272
  25. exploitgraph-1.0.1/exploitgraph/modules/cloud/iam_enum.py +0 -321
  26. exploitgraph-1.0.1/exploitgraph/modules/cloud/iam_privilege_escalation.py +0 -515
  27. exploitgraph-1.0.1/exploitgraph/modules/cloud/metadata_check.py +0 -315
  28. exploitgraph-1.0.1/exploitgraph/modules/cloud/s3_enum.py +0 -469
  29. exploitgraph-1.0.1/exploitgraph/modules/discovery/__init__.py +0 -0
  30. exploitgraph-1.0.1/exploitgraph/modules/discovery/http_enum.py +0 -235
  31. exploitgraph-1.0.1/exploitgraph/modules/discovery/subdomain_enum.py +0 -260
  32. exploitgraph-1.0.1/exploitgraph/modules/exploitation/__init__.py +0 -0
  33. exploitgraph-1.0.1/exploitgraph/modules/exploitation/api_exploit.py +0 -403
  34. exploitgraph-1.0.1/exploitgraph/modules/exploitation/jwt_attack.py +0 -346
  35. exploitgraph-1.0.1/exploitgraph/modules/exploitation/ssrf_scanner.py +0 -258
  36. exploitgraph-1.0.1/exploitgraph/modules/reporting/__init__.py +0 -0
  37. exploitgraph-1.0.1/exploitgraph/modules/reporting/html_report.py +0 -446
  38. exploitgraph-1.0.1/exploitgraph/modules/reporting/json_export.py +0 -107
  39. exploitgraph-1.0.1/exploitgraph/modules/secrets/__init__.py +0 -0
  40. exploitgraph-1.0.1/exploitgraph/modules/secrets/file_secrets.py +0 -358
  41. exploitgraph-1.0.1/exploitgraph/modules/secrets/git_secrets.py +0 -267
  42. exploitgraph-1.0.1/exploitgraph.egg-info/SOURCES.txt +0 -51
  43. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/LICENSE +0 -0
  44. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/README.md +0 -0
  45. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph/data/wordlists/backup_files.txt +0 -0
  46. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph/data/wordlists/common_paths.txt +0 -0
  47. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph/data/wordlists/s3_buckets.txt +0 -0
  48. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph/data/wordlists/subdomains.txt +0 -0
  49. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph.egg-info/dependency_links.txt +0 -0
  50. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph.egg-info/entry_points.txt +0 -0
  51. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph.egg-info/requires.txt +0 -0
  52. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/exploitgraph.egg-info/top_level.txt +0 -0
  53. {exploitgraph-1.0.1 → exploitgraph-1.0.2}/setup.cfg +0 -0
  54. {exploitgraph-1.0.1 → 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.1
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exploitgraph
3
- Version: 1.0.1
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "exploitgraph"
7
- version = "1.0.1"
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*"]
53
+ [tool.setuptools]
54
+ packages = ["exploitgraph"]
56
55
 
57
56
  [tool.setuptools.package-data]
58
57
  "*" = ["data/wordlists/*.txt", "data/templates/*.j2", "*.yaml", "*.yml"]
@@ -1,99 +0,0 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
- import os
4
- import sys
5
-
6
-
7
- def main():
8
- import argparse
9
-
10
- parser = argparse.ArgumentParser(
11
- prog="exploitgraph",
12
- description="ExploitGraph — Automated Attack Path Discovery & Exploitation Framework",
13
- )
14
-
15
- parser.add_argument("-t", "--target")
16
- parser.add_argument("-m", "--module")
17
- parser.add_argument("--auto", action="store_true")
18
- parser.add_argument("--mode", choices=["offensive", "defensive"], default="offensive")
19
- parser.add_argument("--output-dir", default="reports")
20
- parser.add_argument("--list-modules", action="store_true")
21
- parser.add_argument("--session")
22
- parser.add_argument("--workspace")
23
- parser.add_argument("-q", "--quiet", action="store_true")
24
- parser.add_argument("--version", action="version", version="ExploitGraph 1.0.1")
25
-
26
- args = parser.parse_args()
27
-
28
- # ✅ FIXED IMPORTS
29
- from exploitgraph.core.logger import log
30
- from exploitgraph.core.module_loader import loader
31
- from exploitgraph.core.session_manager import session_manager
32
- from exploitgraph.core.console import print_banner, ExploitGraphConsole
33
-
34
- if not args.quiet:
35
- os.system("clear")
36
- print_banner()
37
-
38
- loader.discover()
39
-
40
- # LIST MODULES
41
- if args.list_modules:
42
- for cat, mods in loader.all_modules().items():
43
- if mods:
44
- print(f"\n {cat.upper()}")
45
- for m in mods:
46
- print(f" {m['path']:<38} {m['description'][:48]}")
47
- print(f"\n Total: {loader.count()} modules")
48
- sys.exit(0)
49
-
50
- # SESSION
51
- if args.session:
52
- s = session_manager.switch(args.session)
53
- if not s:
54
- log.error(f"Session not found: {args.session}")
55
- sys.exit(1)
56
- log.success(f"Resumed: {args.session}")
57
-
58
- elif args.target:
59
- t = args.target if args.target.startswith(("http://", "https://")) else "http://" + args.target
60
- s = session_manager.new(t, args.workspace or "session", args.mode)
61
- log.success(f"Session: {s.session_id} | Target: {t} | Mode: {args.mode}")
62
-
63
- else:
64
- s = session_manager.new("http://127.0.0.1:5000", "default", args.mode)
65
-
66
- # SINGLE MODULE
67
- if args.module and not args.auto:
68
- mod = loader.instantiate(args.module)
69
- if not mod:
70
- log.error(f"Module not found: {args.module}")
71
- sys.exit(1)
72
-
73
- for opt in ("TARGET", "MODE"):
74
- if opt in mod.OPTIONS:
75
- mod.set_option(opt, s.target if opt == "TARGET" else args.mode)
76
-
77
- ok, err = mod.validate(s)
78
- if not ok:
79
- log.error(err)
80
- sys.exit(1)
81
-
82
- result = mod.run(s)
83
- sys.exit(0 if result.success else 1)
84
-
85
- console = ExploitGraphConsole()
86
-
87
- if args.auto:
88
- console._mode = args.mode
89
- console._run_auto_chain()
90
- sys.exit(0)
91
-
92
- if args.target:
93
- log.info("Type 'run auto' to start the full attack chain.\n")
94
-
95
- console.cmdloop()
96
-
97
-
98
- if __name__ == "__main__":
99
- main()
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 exploitgraph.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 exploitgraph.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)
@@ -1,83 +0,0 @@
1
- """ExploitGraph - YAML configuration loader."""
2
- from __future__ import annotations
3
- import os
4
- from pathlib import Path
5
- from typing import Any
6
-
7
- BASE_DIR = Path(__file__).parent.parent
8
- CONFIG_FILE = BASE_DIR / "config.yaml"
9
-
10
- DEFAULTS = {
11
- "framework": {"version":"1.0.0","max_threads":10,"timeout":8,"retry":2,
12
- "user_agent":"ExploitGraph/1.0 (Security Research)",
13
- "output_dir":"reports","log_level":"INFO","verify_ssl":False},
14
- "wordlists": {"http_paths":"data/wordlists/common_paths.txt",
15
- "s3_buckets":"data/wordlists/s3_buckets.txt",
16
- "backup_files":"data/wordlists/backup_files.txt",
17
- "subdomains":"data/wordlists/subdomains.txt"},
18
- "aws": {"profile":None,"region":"us-east-1","enabled":False},
19
- "reporting": {"formats":["html","json"],"cvss_minimum":0.0,"open_browser":False},
20
- }
21
-
22
-
23
- class Config:
24
- def __init__(self):
25
- import copy; self._data = copy.deepcopy(DEFAULTS)
26
- self._load()
27
-
28
- def _load(self):
29
- try:
30
- import yaml
31
- if CONFIG_FILE.exists():
32
- with open(CONFIG_FILE) as f:
33
- self._merge(self._data, yaml.safe_load(f) or {})
34
- except ImportError:
35
- pass
36
- for k, v in os.environ.items():
37
- if k.startswith("EG_"):
38
- parts = k[3:].lower().split("_", 1)
39
- if len(parts) == 2 and parts[0] in self._data:
40
- if isinstance(self._data[parts[0]], dict):
41
- self._data[parts[0]][parts[1]] = v
42
-
43
- def _merge(self, base, override):
44
- for k, v in override.items():
45
- if k in base and isinstance(base[k], dict) and isinstance(v, dict):
46
- self._merge(base[k], v)
47
- else:
48
- base[k] = v
49
-
50
- def __getitem__(self, k): return self._data[k]
51
-
52
- def get(self, dotpath: str, default: Any = None) -> Any:
53
- node = self._data
54
- for p in dotpath.split("."):
55
- if not isinstance(node, dict) or p not in node: return default
56
- node = node[p]
57
- return node
58
-
59
- def set(self, dotpath: str, value: Any):
60
- parts = dotpath.split(".")
61
- node = self._data
62
- for p in parts[:-1]: node = node.setdefault(p, {})
63
- node[parts[-1]] = value
64
-
65
- def wordlist_path(self, name: str) -> Path:
66
- rel = self.get(f"wordlists.{name}", "")
67
- return BASE_DIR / rel if rel else BASE_DIR / "data" / "wordlists" / f"{name}.txt"
68
-
69
- @property
70
- def timeout(self) -> int: return int(self.get("framework.timeout", 8))
71
- @property
72
- def max_threads(self) -> int: return int(self.get("framework.max_threads", 10))
73
- @property
74
- def user_agent(self) -> str: return self.get("framework.user_agent","ExploitGraph/1.0")
75
- @property
76
- def verify_ssl(self) -> bool: return bool(self.get("framework.verify_ssl", False))
77
- @property
78
- def aws_enabled(self) -> bool:
79
- try: import boto3; return bool(self.get("aws.enabled", False))
80
- except ImportError: return False
81
-
82
-
83
- cfg = Config()