ctf-agent 1.2.0

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 (152) hide show
  1. package/AGENTS.md +131 -0
  2. package/Dockerfile +192 -0
  3. package/README.md +522 -0
  4. package/agents/ctf-analyzer.md +58 -0
  5. package/agents/ctf-controller.md +151 -0
  6. package/agents/ctf-speedrun.md +74 -0
  7. package/bin/ctf-agent.js +119 -0
  8. package/docker-compose.yml +13 -0
  9. package/mcp_config.json +19 -0
  10. package/package.json +44 -0
  11. package/references/ctf-triage-ladder.md +68 -0
  12. package/references/exploit-databases.md +575 -0
  13. package/references/llm-safety-and-policy-compliance.md +173 -0
  14. package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
  15. package/references/security-events-and-intelligence.md +566 -0
  16. package/references/version-matrix.md +59 -0
  17. package/rules/ctf-execution-rules.md +44 -0
  18. package/rules/ctf-safety-framing-rules.md +140 -0
  19. package/rules/flag-validation-rules.md +25 -0
  20. package/scripts/__init__.py +1 -0
  21. package/scripts/ctf-tools.lock +205 -0
  22. package/scripts/ctf_init.py +1026 -0
  23. package/scripts/ctf_update.py +583 -0
  24. package/scripts/ctfd_client.py +81 -0
  25. package/scripts/cve_lookup.py +453 -0
  26. package/scripts/extract_flags.py +155 -0
  27. package/scripts/install_as_agent.py +278 -0
  28. package/scripts/install_ctf_tools.sh +603 -0
  29. package/scripts/parallel_triage.py +395 -0
  30. package/scripts/prompt_policy_sanitizer.py +283 -0
  31. package/scripts/scope_guard.py +444 -0
  32. package/scripts/skill_validator.py +504 -0
  33. package/scripts/workspace_cleaner.py +141 -0
  34. package/skills/ctf-ai-ml/SKILL.md +120 -0
  35. package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
  36. package/skills/ctf-ai-ml/llm-attacks.md +487 -0
  37. package/skills/ctf-ai-ml/model-attacks.md +422 -0
  38. package/skills/ctf-crypto/SKILL.md +320 -0
  39. package/skills/ctf-crypto/advanced-math.md +798 -0
  40. package/skills/ctf-crypto/classic-ciphers.md +651 -0
  41. package/skills/ctf-crypto/ecc-attacks.md +347 -0
  42. package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
  43. package/skills/ctf-crypto/exotic-crypto.md +528 -0
  44. package/skills/ctf-crypto/historical.md +113 -0
  45. package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
  46. package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
  47. package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
  48. package/skills/ctf-crypto/modern-ciphers.md +649 -0
  49. package/skills/ctf-crypto/prng-attacks.md +257 -0
  50. package/skills/ctf-crypto/prng.md +664 -0
  51. package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
  52. package/skills/ctf-crypto/rsa-attacks.md +487 -0
  53. package/skills/ctf-crypto/stream-ciphers.md +390 -0
  54. package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
  55. package/skills/ctf-forensics/3d-printing.md +121 -0
  56. package/skills/ctf-forensics/SKILL.md +379 -0
  57. package/skills/ctf-forensics/disk-advanced.md +497 -0
  58. package/skills/ctf-forensics/disk-and-memory.md +491 -0
  59. package/skills/ctf-forensics/disk-recovery.md +699 -0
  60. package/skills/ctf-forensics/linux-forensics.md +511 -0
  61. package/skills/ctf-forensics/network-advanced.md +583 -0
  62. package/skills/ctf-forensics/network.md +645 -0
  63. package/skills/ctf-forensics/peripheral-capture.md +287 -0
  64. package/skills/ctf-forensics/signals-and-hardware.md +713 -0
  65. package/skills/ctf-forensics/steganography.md +694 -0
  66. package/skills/ctf-forensics/stego-advanced-2.md +475 -0
  67. package/skills/ctf-forensics/stego-advanced.md +481 -0
  68. package/skills/ctf-forensics/stego-image.md +691 -0
  69. package/skills/ctf-forensics/windows.md +625 -0
  70. package/skills/ctf-malware/SKILL.md +181 -0
  71. package/skills/ctf-malware/c2-and-protocols.md +274 -0
  72. package/skills/ctf-malware/pe-and-dotnet.md +108 -0
  73. package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
  74. package/skills/ctf-misc/SKILL.md +498 -0
  75. package/skills/ctf-misc/bashjails.md +323 -0
  76. package/skills/ctf-misc/ctfd-navigation.md +465 -0
  77. package/skills/ctf-misc/dns.md +255 -0
  78. package/skills/ctf-misc/encodings-advanced.md +504 -0
  79. package/skills/ctf-misc/encodings.md +431 -0
  80. package/skills/ctf-misc/games-and-vms-2.md +254 -0
  81. package/skills/ctf-misc/games-and-vms-3.md +690 -0
  82. package/skills/ctf-misc/games-and-vms-4.md +229 -0
  83. package/skills/ctf-misc/games-and-vms.md +529 -0
  84. package/skills/ctf-misc/linux-privesc.md +333 -0
  85. package/skills/ctf-misc/pyjails.md +671 -0
  86. package/skills/ctf-misc/rf-sdr.md +91 -0
  87. package/skills/ctf-osint/SKILL.md +198 -0
  88. package/skills/ctf-osint/geolocation-and-media.md +464 -0
  89. package/skills/ctf-osint/social-media.md +312 -0
  90. package/skills/ctf-osint/web-and-dns.md +341 -0
  91. package/skills/ctf-pwn/SKILL.md +214 -0
  92. package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
  93. package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
  94. package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
  95. package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
  96. package/skills/ctf-pwn/advanced-exploits.md +773 -0
  97. package/skills/ctf-pwn/advanced.md +326 -0
  98. package/skills/ctf-pwn/field-notes.md +245 -0
  99. package/skills/ctf-pwn/format-string.md +694 -0
  100. package/skills/ctf-pwn/heap-fsop.md +285 -0
  101. package/skills/ctf-pwn/heap-techniques-2.md +333 -0
  102. package/skills/ctf-pwn/heap-techniques.md +513 -0
  103. package/skills/ctf-pwn/kernel-bypass.md +421 -0
  104. package/skills/ctf-pwn/kernel-techniques.md +366 -0
  105. package/skills/ctf-pwn/kernel.md +636 -0
  106. package/skills/ctf-pwn/overflow-basics.md +611 -0
  107. package/skills/ctf-pwn/rop-advanced.md +725 -0
  108. package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
  109. package/skills/ctf-pwn/sandbox-escape.md +313 -0
  110. package/skills/ctf-reverse/SKILL.md +163 -0
  111. package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
  112. package/skills/ctf-reverse/anti-analysis.md +693 -0
  113. package/skills/ctf-reverse/field-notes.md +376 -0
  114. package/skills/ctf-reverse/languages-compiled.md +666 -0
  115. package/skills/ctf-reverse/languages-platforms.md +592 -0
  116. package/skills/ctf-reverse/languages.md +553 -0
  117. package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
  118. package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
  119. package/skills/ctf-reverse/patterns-ctf.md +670 -0
  120. package/skills/ctf-reverse/patterns-runtime.md +274 -0
  121. package/skills/ctf-reverse/patterns.md +572 -0
  122. package/skills/ctf-reverse/platforms-hardware.md +387 -0
  123. package/skills/ctf-reverse/platforms.md +664 -0
  124. package/skills/ctf-reverse/tools-advanced-2.md +421 -0
  125. package/skills/ctf-reverse/tools-advanced.md +407 -0
  126. package/skills/ctf-reverse/tools-dynamic.md +679 -0
  127. package/skills/ctf-reverse/tools-emulation.md +319 -0
  128. package/skills/ctf-reverse/tools.md +573 -0
  129. package/skills/ctf-web/SKILL.md +153 -0
  130. package/skills/ctf-web/auth-and-access-2.md +82 -0
  131. package/skills/ctf-web/auth-and-access.md +783 -0
  132. package/skills/ctf-web/auth-infra.md +321 -0
  133. package/skills/ctf-web/auth-jwt.md +186 -0
  134. package/skills/ctf-web/client-side-advanced.md +739 -0
  135. package/skills/ctf-web/client-side.md +529 -0
  136. package/skills/ctf-web/cves.md +373 -0
  137. package/skills/ctf-web/field-notes.md +482 -0
  138. package/skills/ctf-web/node-and-prototype.md +200 -0
  139. package/skills/ctf-web/server-side-2.md +337 -0
  140. package/skills/ctf-web/server-side-advanced-2.md +559 -0
  141. package/skills/ctf-web/server-side-advanced-3.md +125 -0
  142. package/skills/ctf-web/server-side-advanced-4.md +480 -0
  143. package/skills/ctf-web/server-side-advanced.md +378 -0
  144. package/skills/ctf-web/server-side-deser.md +443 -0
  145. package/skills/ctf-web/server-side-exec-2.md +799 -0
  146. package/skills/ctf-web/server-side-exec.md +457 -0
  147. package/skills/ctf-web/server-side.md +629 -0
  148. package/skills/ctf-web/sql-injection.md +790 -0
  149. package/skills/ctf-web/web3.md +374 -0
  150. package/skills/ctf-writeup/SKILL.md +90 -0
  151. package/skills/solve-challenge/SKILL.md +269 -0
  152. package/skills.json +16 -0
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CVE & Threat Intelligence Lookup Utility (v2.1)
4
+ Authoritative vulnerability intelligence pipeline for CTF Agents & Security Researchers:
5
+ - CVE List V5 / CVE Services API (cveawg.mitre.org)
6
+ - FIRST EPSS API (api.first.org)
7
+ - CISA KEV (Known Exploited Vulnerabilities) catalog with in-memory caching
8
+ - Exploit reference categorization (PoC, Exploit-DB, GitHub, Packet Storm)
9
+ - Decoupled CTF Challenge Relevance Layer
10
+ - Structured JSON output mode (--json)
11
+ """
12
+
13
+ import sys
14
+ import os
15
+ import re
16
+ import time
17
+ import json
18
+ import urllib.request
19
+ import urllib.error
20
+ from functools import lru_cache
21
+ from typing import Optional, Dict, Any, List, Tuple
22
+
23
+ # Safe UTF-8 output on Windows consoles
24
+ if hasattr(sys.stdout, "reconfigure"):
25
+ try:
26
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
27
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
28
+ except Exception:
29
+ pass
30
+
31
+ USER_AGENT = "CTF-Agent-ThreatIntel/2.1"
32
+ TIMEOUT = 10
33
+ CVE_PATTERN = re.compile(r"^CVE-\d{4}-\d{4,}$")
34
+
35
+ EXPLOIT_URL_PATTERNS = [
36
+ re.compile(r"exploit-db\.com", re.I),
37
+ re.compile(r"packetstormsecurity\.com", re.I),
38
+ re.compile(r"github\.com/[^/]+/(?:poc|exploit|cve-)", re.I),
39
+ re.compile(r"0day\.today", re.I),
40
+ re.compile(r"seclists\.org/fulldisclosure", re.I),
41
+ re.compile(r"rapid7\.com/db", re.I),
42
+ re.compile(r"sploitus\.com", re.I),
43
+ re.compile(r"project-zero", re.I),
44
+ ]
45
+ EXPLOIT_TAGS = {"exploit", "technical-description", "third-party-advisory"}
46
+
47
+
48
+ def normalize_cve_id(cve_id: str) -> str:
49
+ """Validates and normalizes CVE identifier format (CVE-YYYY-NNNN)."""
50
+ if not cve_id or not isinstance(cve_id, str) or not cve_id.strip():
51
+ raise ValueError(f"Invalid CVE identifier: '{cve_id}'. Expected format: CVE-YYYY-NNNN")
52
+
53
+ cleaned = cve_id.strip().upper()
54
+ if not cleaned.startswith("CVE-"):
55
+ cleaned = "CVE-" + cleaned
56
+
57
+ if not CVE_PATTERN.fullmatch(cleaned):
58
+ raise ValueError(f"Invalid CVE identifier: '{cve_id}'. Expected format: CVE-YYYY-NNNN")
59
+
60
+ return cleaned
61
+
62
+
63
+ def fetch_json(url: str, retries: int = 2, backoff: float = 0.5) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
64
+ """
65
+ Fetch JSON payload from a remote endpoint with retry logic and error classification.
66
+ Returns (data_dict, error_message).
67
+ """
68
+ req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
69
+
70
+ for attempt in range(retries + 1):
71
+ try:
72
+ with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
73
+ content = resp.read().decode("utf-8")
74
+ return json.loads(content), None
75
+ except urllib.error.HTTPError as exc:
76
+ if exc.code == 404:
77
+ return None, f"HTTP 404: Resource not found at {url}"
78
+ if exc.code == 429 and attempt < retries:
79
+ time.sleep(backoff * (2 ** attempt))
80
+ continue
81
+ if exc.code in (500, 502, 503, 504) and attempt < retries:
82
+ time.sleep(backoff * (2 ** attempt))
83
+ continue
84
+ return None, f"HTTP error {exc.code} while fetching {url}: {exc.reason}"
85
+ except urllib.error.URLError as exc:
86
+ if attempt < retries:
87
+ time.sleep(backoff * (2 ** attempt))
88
+ continue
89
+ return None, f"Network error connecting to {url}: {exc.reason}"
90
+ except json.JSONDecodeError as exc:
91
+ return None, f"Malformed JSON response from {url}: {exc}"
92
+ except Exception as exc:
93
+ return None, f"Unexpected error fetching {url}: {exc}"
94
+
95
+ return None, f"Failed to fetch {url} after {retries + 1} attempts"
96
+
97
+
98
+ def get_cve_details(cve_id: str) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
99
+ """Query official CVE Services API (CVE List V5 format)."""
100
+ url = f"https://cveawg.mitre.org/api/cve/{cve_id}"
101
+ return fetch_json(url)
102
+
103
+
104
+ def get_epss_score(cve_id: str) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
105
+ """Query FIRST EPSS API for exploitation probability and percentile."""
106
+ url = f"https://api.first.org/data/v1/epss?cve={cve_id}"
107
+ data, err = fetch_json(url)
108
+ if data and "data" in data and len(data["data"]) > 0:
109
+ return data["data"][0], None
110
+ return None, err or "No EPSS record returned"
111
+
112
+
113
+ @lru_cache(maxsize=1)
114
+ def get_cisa_kev_catalog() -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
115
+ """
116
+ Downloads and caches the official CISA KEV catalog in memory.
117
+ Cached across lookups to avoid downloading multi-megabyte feeds repeatedly.
118
+ """
119
+ url = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
120
+ return fetch_json(url)
121
+
122
+
123
+ def check_cisa_kev(cve_id: str) -> Dict[str, Any]:
124
+ """Check if CVE is listed in CISA Known Exploited Vulnerabilities catalog."""
125
+ data, err = get_cisa_kev_catalog()
126
+ if not data:
127
+ return {"listed": False, "error": err or "Unable to reach CISA KEV feed"}
128
+
129
+ vulns = data.get("vulnerabilities", [])
130
+ target_id = cve_id.upper()
131
+ for v in vulns:
132
+ if v.get("cveID", "").upper() == target_id:
133
+ return {
134
+ "listed": True,
135
+ "vendorProject": v.get("vendorProject", "Unknown"),
136
+ "product": v.get("product", "Unknown"),
137
+ "vulnerabilityName": v.get("vulnerabilityName", "N/A"),
138
+ "dateAdded": v.get("dateAdded", "N/A"),
139
+ "requiredAction": v.get("requiredAction", "N/A"),
140
+ "knownRansomwareCampaignUse": v.get("knownRansomwareCampaignUse", "Unknown"),
141
+ "note": "Exploitation has been confirmed historically in the wild",
142
+ }
143
+ return {
144
+ "listed": False,
145
+ "note": "No federal mandate / confirmed broad in-the-wild exploitation recorded in KEV catalog",
146
+ }
147
+
148
+
149
+ def classify_references(references: List[Dict[str, Any]]) -> Dict[str, Any]:
150
+ """Classifies external references into general advisories vs public exploit PoCs."""
151
+ exploit_urls: List[str] = []
152
+ other_urls: List[str] = []
153
+
154
+ for ref in references:
155
+ url = ref.get("url", "")
156
+ tags = set(ref.get("tags", []))
157
+ is_exploit = bool(tags & EXPLOIT_TAGS) or any(pat.search(url) for pat in EXPLOIT_URL_PATTERNS)
158
+
159
+ if is_exploit:
160
+ exploit_urls.append(url)
161
+ else:
162
+ other_urls.append(url)
163
+
164
+ return {
165
+ "total": len(references),
166
+ "exploit_related": len(exploit_urls),
167
+ "exploit_urls": exploit_urls,
168
+ "general_urls": other_urls[:5],
169
+ }
170
+
171
+
172
+ def assess_ctf_relevance(
173
+ cve_data: Optional[Dict[str, Any]],
174
+ epss_info: Optional[Dict[str, Any]],
175
+ kev_info: Dict[str, Any],
176
+ ref_info: Dict[str, Any],
177
+ ) -> Dict[str, Any]:
178
+ """
179
+ Computes educational CTF challenge relevance heuristic score and signals.
180
+ Note: Decoupled strictly from EPSS (real-world probability) and CVSS (severity).
181
+ """
182
+ score = 0.0
183
+ signals: List[str] = []
184
+
185
+ # 1. Public PoC / Exploit availability (strongest CTF indicator)
186
+ if ref_info.get("exploit_related", 0) > 0:
187
+ score += 0.35
188
+ signals.append(f"public_exploit_poc_available ({ref_info['exploit_related']} references)")
189
+
190
+ # 2. Historical confirmed exploitation (CISA KEV)
191
+ if kev_info.get("listed"):
192
+ score += 0.25
193
+ signals.append("cisa_kev_listed")
194
+
195
+ # 3. CVSS metrics extraction
196
+ cvss_score = 0.0
197
+ cvss_vector = ""
198
+ if cve_data:
199
+ cna = cve_data.get("containers", {}).get("cna", {})
200
+ metrics = cna.get("metrics", [])
201
+ for m in metrics:
202
+ for cvss_key in ("cvssV4_0", "cvssV3_1", "cvssV3_0", "cvssV2_0"):
203
+ if cvss_key in m:
204
+ cvss = m[cvss_key]
205
+ try:
206
+ cvss_score = max(cvss_score, float(cvss.get("baseScore", 0.0)))
207
+ except (ValueError, TypeError):
208
+ pass
209
+ cvss_vector = cvss.get("vectorString", cvss_vector)
210
+
211
+ if cvss_score >= 7.0:
212
+ score += 0.20
213
+ signals.append(f"high_severity_cvss ({cvss_score})")
214
+
215
+ if "AV:N" in cvss_vector:
216
+ score += 0.15
217
+ signals.append("network_attack_vector (AV:N)")
218
+
219
+ if "PR:N" in cvss_vector or "PR:L" in cvss_vector:
220
+ score += 0.10
221
+ signals.append("low_or_none_privileges_required")
222
+
223
+ if "UI:N" in cvss_vector:
224
+ score += 0.10
225
+ signals.append("no_user_interaction_required")
226
+
227
+ # 4. Web / RCE / Memory corruption keywords
228
+ text_content = ""
229
+ if cve_data:
230
+ cna = cve_data.get("containers", {}).get("cna", {})
231
+ title = cna.get("title", "")
232
+ desc = " ".join(d.get("value", "") for d in cna.get("descriptions", []))
233
+ text_content = f"{title} {desc}".lower()
234
+
235
+ web_keywords = ["remote code execution", "rce", "sql injection", "command injection", "buffer overflow", "deserialization", "authentication bypass", "memory corruption"]
236
+ matched_kws = [kw for kw in web_keywords if kw in text_content]
237
+ if matched_kws:
238
+ score += 0.15
239
+ signals.append(f"challenge_vulnerability_pattern ({', '.join(matched_kws[:3])})")
240
+
241
+ normalized_score = min(1.0, round(score, 2))
242
+ return {
243
+ "score": normalized_score,
244
+ "signals": signals,
245
+ "note": "CTF relevance is an educational challenge heuristic based on public exploit availability and network reachability; it is strictly distinct from EPSS and CVSS.",
246
+ }
247
+
248
+
249
+ def lookup_cve_threat_intel(cve_id: str) -> Dict[str, Any]:
250
+ """End-to-end vulnerability threat intelligence pipeline returning structured data."""
251
+ norm_cve = normalize_cve_id(cve_id)
252
+ errors: List[str] = []
253
+
254
+ # 1. Fetch MITRE CVE V5 Record
255
+ cve_data, cve_err = get_cve_details(norm_cve)
256
+ if cve_err:
257
+ errors.append(cve_err)
258
+
259
+ if not cve_data:
260
+ return {
261
+ "status": "error",
262
+ "cve": norm_cve,
263
+ "error": cve_err or f"Record for {norm_cve} not found in CVE List V5 repository.",
264
+ }
265
+
266
+ cve_metadata = cve_data.get("cveMetadata", {})
267
+ state = cve_metadata.get("state", "UNKNOWN")
268
+ cna_container = cve_data.get("containers", {}).get("cna", {})
269
+
270
+ title = cna_container.get("title", "No title provided by CNA")
271
+ descriptions = cna_container.get("descriptions", [])
272
+ desc_text = "No description available."
273
+ for d in descriptions:
274
+ if d.get("lang") in ("en", "en-US", None):
275
+ desc_text = d.get("value", desc_text)
276
+ break
277
+
278
+ # 2. Extract CVSS
279
+ cvss_info: Dict[str, Any] = {}
280
+ metrics = cna_container.get("metrics", [])
281
+ for m in metrics:
282
+ for cvss_key in ("cvssV4_0", "cvssV3_1", "cvssV3_0", "cvssV2_0"):
283
+ if cvss_key in m:
284
+ c = m[cvss_key]
285
+ cvss_info = {
286
+ "version": cvss_key.replace("cvssV", "").replace("_", "."),
287
+ "score": c.get("baseScore", "N/A"),
288
+ "severity": c.get("baseSeverity", "N/A"),
289
+ "vector": c.get("vectorString", "N/A"),
290
+ }
291
+ break
292
+ if cvss_info:
293
+ break
294
+
295
+ # 3. Affected Products
296
+ affected_list = []
297
+ for aff in cna_container.get("affected", [])[:5]:
298
+ v_list = [f"{v.get('version', '')} ({v.get('status', '')})" for v in aff.get("versions", [])[:3]]
299
+ affected_list.append({
300
+ "vendor": aff.get("vendor", "Unknown"),
301
+ "product": aff.get("product", "Unknown"),
302
+ "versions": v_list,
303
+ })
304
+
305
+ # 4. FIRST EPSS
306
+ epss_raw, epss_err = get_epss_score(norm_cve)
307
+ if epss_err:
308
+ errors.append(epss_err)
309
+
310
+ epss_info: Dict[str, Any] = {}
311
+ if epss_raw:
312
+ epss_prob = float(epss_raw.get("epss", 0.0))
313
+ epss_pct = float(epss_raw.get("percentile", 0.0))
314
+ epss_info = {
315
+ "score": epss_prob,
316
+ "percentile": epss_pct,
317
+ "note": "EPSS estimates the probability of exploitation in the wild; it is not proof of active exploitation and should not be treated as a CTF exploitability score.",
318
+ }
319
+
320
+ # 5. CISA KEV
321
+ kev_info = check_cisa_kev(norm_cve)
322
+
323
+ # 6. References Classification
324
+ raw_refs = cna_container.get("references", [])
325
+ ref_info = classify_references(raw_refs)
326
+
327
+ # 7. CTF Relevance Assessment
328
+ relevance = assess_ctf_relevance(cve_data, epss_raw, kev_info, ref_info)
329
+
330
+ return {
331
+ "status": "success",
332
+ "cve": norm_cve,
333
+ "state": state,
334
+ "cna": cve_metadata.get("assignerShortName", "Unknown"),
335
+ "title": title,
336
+ "description": desc_text.strip(),
337
+ "affected": affected_list,
338
+ "cvss": cvss_info,
339
+ "epss": epss_info,
340
+ "kev": kev_info,
341
+ "references": ref_info,
342
+ "ctf_relevance": relevance,
343
+ "errors": errors,
344
+ }
345
+
346
+
347
+ def display_human_summary(intel: Dict[str, Any]) -> None:
348
+ """Renders formatted console output from structured threat intelligence data."""
349
+ cve_id = intel["cve"]
350
+ print("\n" + "=" * 70)
351
+ print(f"[*] VULNERABILITY THREAT INTELLIGENCE: {cve_id}")
352
+ print("=" * 70)
353
+
354
+ print(f"\n[+] STATUS: {intel.get('state')} | CNA: {intel.get('cna')}")
355
+ print(f"[+] TITLE : {intel.get('title')}\n")
356
+ print(f"[+] DESCRIPTION:\n {intel.get('description')}\n")
357
+
358
+ # Affected products
359
+ affected = intel.get("affected", [])
360
+ if affected:
361
+ print("[+] AFFECTED PRODUCTS & VERSIONS:")
362
+ for aff in affected:
363
+ ver_str = ", ".join(aff["versions"]) if aff["versions"] else "Unspecified"
364
+ print(f" - {aff['vendor']} / {aff['product']} -> {ver_str}")
365
+ print()
366
+
367
+ # Metrics
368
+ cvss = intel.get("cvss", {})
369
+ if cvss:
370
+ print(f"[+] CVSS BASE METRICS (v{cvss.get('version', '3.x')}):")
371
+ print(f" - Score: {cvss.get('score')} ({cvss.get('severity')}) | Vector: {cvss.get('vector')}\n")
372
+
373
+ # EPSS
374
+ epss = intel.get("epss", {})
375
+ print("[+] EPSS PROBABILITY (FIRST.org):")
376
+ if epss and "score" in epss:
377
+ score = epss["score"]
378
+ pct = epss["percentile"] * 100.0
379
+ print(f" - Exploitation Probability (30-day): {score:.4f} ({score * 100:.2f}%)")
380
+ print(f" - Percentile: {pct:.2f}%")
381
+ print(f" - Note: {epss.get('note')}")
382
+ else:
383
+ print(" - No EPSS data available.")
384
+ print()
385
+
386
+ # CISA KEV
387
+ kev = intel.get("kev", {})
388
+ print("[+] CISA KEV (KNOWN EXPLOITED VULNERABILITIES):")
389
+ if kev.get("listed"):
390
+ print(f" - [HIGH] Listed in CISA KEV: YES (exploitation has been confirmed)")
391
+ print(f" - Vulnerability Name: {kev.get('vulnerabilityName')}")
392
+ print(f" - Date Added: {kev.get('dateAdded')}")
393
+ print(f" - Known Ransomware Campaign Use: {kev.get('knownRansomwareCampaignUse')}")
394
+ elif "error" in kev:
395
+ print(f" - Status: {kev.get('error')}")
396
+ else:
397
+ print(f" - Listed in CISA KEV: NO ({kev.get('note')})")
398
+ print()
399
+
400
+ # Exploit References
401
+ refs = intel.get("references", {})
402
+ print(f"[+] REFERENCES ({refs.get('total', 0)} total, {refs.get('exploit_related', 0)} exploit-related):")
403
+ if refs.get("exploit_urls"):
404
+ print(" [!] Public PoC / Exploit References:")
405
+ for e_url in refs["exploit_urls"][:6]:
406
+ print(f" * {e_url}")
407
+ for g_url in refs.get("general_urls", [])[:3]:
408
+ print(f" - {g_url}")
409
+ print()
410
+
411
+ # CTF Relevance
412
+ ctf_rel = intel.get("ctf_relevance", {})
413
+ print(f"[+] CTF CHALLENGE RELEVANCE (Heuristic Score: {ctf_rel.get('score', 0.0):.2f}/1.0):")
414
+ for sig in ctf_rel.get("signals", []):
415
+ print(f" - [Signal] {sig}")
416
+ print(f" - Note: {ctf_rel.get('note')}")
417
+ print("=" * 70 + "\n")
418
+
419
+
420
+ def main() -> None:
421
+ import argparse
422
+ parser = argparse.ArgumentParser(description="CTF-Agent Vulnerability Threat Intelligence Utility v2.1")
423
+ parser.add_argument("cve", help="CVE identifier (e.g. CVE-2023-44487 or 2023-44487)")
424
+ parser.add_argument("--json", action="store_true", help="Output machine-readable JSON structure")
425
+
426
+ args = parser.parse_args()
427
+
428
+ try:
429
+ norm_cve = normalize_cve_id(args.cve)
430
+ except ValueError as exc:
431
+ if args.json:
432
+ print(json.dumps({"status": "error", "error": str(exc)}, indent=2))
433
+ else:
434
+ print(f"[-] Input Error: {exc}", file=sys.stderr)
435
+ sys.exit(1)
436
+
437
+ intel = lookup_cve_threat_intel(norm_cve)
438
+
439
+ if intel.get("status") == "error":
440
+ if args.json:
441
+ print(json.dumps(intel, indent=2))
442
+ else:
443
+ print(f"[-] Error: {intel.get('error')}", file=sys.stderr)
444
+ sys.exit(1)
445
+
446
+ if args.json:
447
+ print(json.dumps(intel, indent=2))
448
+ else:
449
+ display_human_summary(intel)
450
+
451
+
452
+ if __name__ == "__main__":
453
+ main()
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Flag Candidate Extraction Engine (v2.1)
4
+ Extracts, normalizes, deduplicates, and scores flag candidates from raw text, command output, or files.
5
+ Supports high-visibility terminal banners for Blitz mode and JSON output for CTFd automation.
6
+ """
7
+
8
+ import sys
9
+ import os
10
+ import re
11
+ import json
12
+ import argparse
13
+ from dataclasses import dataclass, asdict
14
+ from typing import List, Optional
15
+
16
+ # Ensure safe UTF-8 output on Windows consoles
17
+ if hasattr(sys.stdout, "reconfigure"):
18
+ try:
19
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
20
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
21
+ except Exception:
22
+ pass
23
+
24
+ @dataclass
25
+ class FlagCandidate:
26
+ value: str
27
+ source: str
28
+ location: Optional[str]
29
+ confidence: float
30
+
31
+ # Common competition flag patterns with baseline confidence
32
+ DEFAULT_FLAG_PATTERNS = [
33
+ (r'(?<![a-zA-Z0-9_])picoCTF\{[^\}]+\}', 0.95),
34
+ (r'(?<![a-zA-Z0-9_])Dice\{[^\}]+\}', 0.95),
35
+ (r'(?<![a-zA-Z0-9_])SEKAI\{[^\}]+\}', 0.95),
36
+ (r'(?<![a-zA-Z0-9_])HTB\{[^\}]+\}', 0.95),
37
+ (r'(?<![a-zA-Z0-9_])flagyard\{[^\}]+\}', 0.95),
38
+ (r'(?<![a-zA-Z0-9_])FlagY\{[^\}]+\}', 0.95),
39
+ (r'(?<![a-zA-Z0-9_])flag\{[a-zA-Z0-9_\-\.\!\?@#\$%\^&\*\+=\s]+\}', 0.95),
40
+ (r'(?<![a-zA-Z0-9_])FLAG\{[a-zA-Z0-9_\-\.\!\?@#\$%\^&\*\+=\s]+\}', 0.95),
41
+ (r'(?<![a-zA-Z0-9_])CTF\{[a-zA-Z0-9_\-\.\!\?@#\$%\^&\*\+=\s]+\}', 0.95),
42
+ (r'(?<![a-zA-Z0-9_])FLAG-[A-Za-z0-9]{16,64}', 0.85),
43
+ (r'(?<![a-zA-Z0-9_])[0-9a-fA-F]{32}(?![a-zA-Z0-9_])', 0.30), # Raw MD5-like candidate (weak)
44
+ ]
45
+
46
+ def extract_flag_candidates(
47
+ raw_text: str,
48
+ source: str = "terminal",
49
+ location: Optional[str] = None,
50
+ custom_prefix: Optional[str] = None
51
+ ) -> List[FlagCandidate]:
52
+ candidates = []
53
+ seen = set()
54
+
55
+ patterns = list(DEFAULT_FLAG_PATTERNS)
56
+ if custom_prefix:
57
+ custom_regex = rf'{re.escape(custom_prefix)}\{{[^\}}]+\}}'
58
+ patterns.insert(0, (custom_regex, 1.0))
59
+
60
+ for pattern, base_conf in patterns:
61
+ matches = re.findall(pattern, raw_text)
62
+ for m in matches:
63
+ cleaned = m.strip()
64
+ if cleaned not in seen:
65
+ seen.add(cleaned)
66
+ conf = base_conf
67
+ if location and any(k in location for k in ("flag.txt", "/flag", "environ", "response")):
68
+ conf = min(1.0, conf + 0.05)
69
+ candidates.append(FlagCandidate(value=cleaned, source=source, location=location, confidence=conf))
70
+
71
+ return sorted(candidates, key=lambda c: c.confidence, reverse=True)
72
+
73
+ def print_banner(flag: str, confidence: float, source: str):
74
+ """Print high-visibility terminal banner for Blitz mode."""
75
+ width = max(60, len(flag) + 24)
76
+ border = "=" * width
77
+ print(f"\n{border}")
78
+ print(f"[+] FLAG ACQUIRED: {flag}")
79
+ print(f"Confidence: {confidence * 100:.0f}% | Source: {source}")
80
+ print(f"{border}\n")
81
+
82
+ def main():
83
+ parser = argparse.ArgumentParser(description="CTF Flag Extraction & Banner Generator")
84
+ parser.add_argument("text", nargs="*", help="Raw text string to scan for flags")
85
+ parser.add_argument("-f", "--file", help="Read input from a file instead of stdin/args")
86
+ parser.add_argument("--prefix", help="Custom flag prefix (e.g., TETCTF, DEFCON)")
87
+ parser.add_argument("--banner", action="store_true", help="Print high-visibility Blitz flag banner on discovery")
88
+ parser.add_argument("--json", action="store_true", help="Output results in JSON format")
89
+ parser.add_argument("--exit-code", action="store_true", help="Return exit code 0 if flag found, 1 if not")
90
+ parser.add_argument("--submit", type=int, metavar="CHALL_ID", help="Auto-submit top flag to CTFd challenge ID if CTF_URL and CTF_TOKEN exist")
91
+
92
+ args = parser.parse_args()
93
+
94
+ raw_text = ""
95
+ source_name = "cli"
96
+
97
+ if args.file:
98
+ try:
99
+ with open(args.file, "r", errors="ignore") as f:
100
+ raw_text = f.read()
101
+ source_name = f"file:{args.file}"
102
+ except Exception as e:
103
+ print(f"[-] Error reading file {args.file}: {e}", file=sys.stderr)
104
+ sys.exit(1)
105
+ elif args.text:
106
+ raw_text = " ".join(args.text)
107
+ source_name = "cli_args"
108
+ elif not sys.stdin.isatty():
109
+ raw_text = sys.stdin.read()
110
+ source_name = "stdin"
111
+
112
+ results = extract_flag_candidates(raw_text, source=source_name, custom_prefix=args.prefix)
113
+
114
+ if not results:
115
+ if args.json:
116
+ print(json.dumps({"success": False, "candidates": []}))
117
+ else:
118
+ print("[-] No flag candidates identified.")
119
+ sys.exit(1 if args.exit_code else 0)
120
+
121
+ top_candidate = results[0]
122
+
123
+ if args.banner:
124
+ print_banner(top_candidate.value, top_candidate.confidence, top_candidate.source)
125
+
126
+ if args.json:
127
+ output_data = {
128
+ "success": True,
129
+ "top_flag": top_candidate.value,
130
+ "candidates": [asdict(c) for c in results]
131
+ }
132
+ print(json.dumps(output_data, indent=2))
133
+ elif not args.banner:
134
+ print(f"[+] Identified {len(results)} flag candidate(s):")
135
+ for idx, c in enumerate(results, 1):
136
+ print(f" {idx}. [{c.confidence:.2f}] {c.value} (Source: {c.source})")
137
+
138
+ # Optional automated CTFd submission
139
+ if args.submit:
140
+ ctf_url = os.environ.get("CTF_URL")
141
+ ctf_token = os.environ.get("CTF_TOKEN")
142
+ if ctf_url and ctf_token:
143
+ try:
144
+ from ctfd_client import CTFdClient
145
+ client = CTFdClient(ctf_url, ctf_token)
146
+ sub_res = client.submit_flag(args.submit, top_candidate.value)
147
+ print(f"[*] CTFd Submission Result: {sub_res.get('status', 'unknown').upper()} - {sub_res.get('message', '')}")
148
+ except Exception as e:
149
+ print(f"[!] CTFd submission failed: {e}", file=sys.stderr)
150
+
151
+ if args.exit_code:
152
+ sys.exit(0)
153
+
154
+ if __name__ == "__main__":
155
+ main()