aiptx 2.0.2__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (165) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +24 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/ptt.py +406 -0
  8. aipt_v2/agents/state.py +168 -0
  9. aipt_v2/app.py +960 -0
  10. aipt_v2/browser/__init__.py +31 -0
  11. aipt_v2/browser/automation.py +458 -0
  12. aipt_v2/browser/crawler.py +453 -0
  13. aipt_v2/cli.py +321 -0
  14. aipt_v2/compliance/__init__.py +71 -0
  15. aipt_v2/compliance/compliance_report.py +449 -0
  16. aipt_v2/compliance/framework_mapper.py +424 -0
  17. aipt_v2/compliance/nist_mapping.py +345 -0
  18. aipt_v2/compliance/owasp_mapping.py +330 -0
  19. aipt_v2/compliance/pci_mapping.py +297 -0
  20. aipt_v2/config.py +288 -0
  21. aipt_v2/core/__init__.py +43 -0
  22. aipt_v2/core/agent.py +630 -0
  23. aipt_v2/core/llm.py +395 -0
  24. aipt_v2/core/memory.py +305 -0
  25. aipt_v2/core/ptt.py +329 -0
  26. aipt_v2/database/__init__.py +14 -0
  27. aipt_v2/database/models.py +232 -0
  28. aipt_v2/database/repository.py +384 -0
  29. aipt_v2/docker/__init__.py +23 -0
  30. aipt_v2/docker/builder.py +260 -0
  31. aipt_v2/docker/manager.py +222 -0
  32. aipt_v2/docker/sandbox.py +371 -0
  33. aipt_v2/evasion/__init__.py +58 -0
  34. aipt_v2/evasion/request_obfuscator.py +272 -0
  35. aipt_v2/evasion/tls_fingerprint.py +285 -0
  36. aipt_v2/evasion/ua_rotator.py +301 -0
  37. aipt_v2/evasion/waf_bypass.py +439 -0
  38. aipt_v2/execution/__init__.py +23 -0
  39. aipt_v2/execution/executor.py +302 -0
  40. aipt_v2/execution/parser.py +544 -0
  41. aipt_v2/execution/terminal.py +337 -0
  42. aipt_v2/health.py +437 -0
  43. aipt_v2/intelligence/__init__.py +85 -0
  44. aipt_v2/intelligence/auth.py +520 -0
  45. aipt_v2/intelligence/chaining.py +775 -0
  46. aipt_v2/intelligence/cve_aipt.py +334 -0
  47. aipt_v2/intelligence/cve_info.py +1111 -0
  48. aipt_v2/intelligence/rag.py +239 -0
  49. aipt_v2/intelligence/scope.py +442 -0
  50. aipt_v2/intelligence/searchers/__init__.py +5 -0
  51. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  52. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  53. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  54. aipt_v2/intelligence/tools.json +443 -0
  55. aipt_v2/intelligence/triage.py +670 -0
  56. aipt_v2/interface/__init__.py +5 -0
  57. aipt_v2/interface/cli.py +230 -0
  58. aipt_v2/interface/main.py +501 -0
  59. aipt_v2/interface/tui.py +1276 -0
  60. aipt_v2/interface/utils.py +583 -0
  61. aipt_v2/llm/__init__.py +39 -0
  62. aipt_v2/llm/config.py +26 -0
  63. aipt_v2/llm/llm.py +514 -0
  64. aipt_v2/llm/memory.py +214 -0
  65. aipt_v2/llm/request_queue.py +89 -0
  66. aipt_v2/llm/utils.py +89 -0
  67. aipt_v2/models/__init__.py +15 -0
  68. aipt_v2/models/findings.py +295 -0
  69. aipt_v2/models/phase_result.py +224 -0
  70. aipt_v2/models/scan_config.py +207 -0
  71. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  72. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  73. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  74. aipt_v2/monitoring/prometheus.yml +60 -0
  75. aipt_v2/orchestration/__init__.py +52 -0
  76. aipt_v2/orchestration/pipeline.py +398 -0
  77. aipt_v2/orchestration/progress.py +300 -0
  78. aipt_v2/orchestration/scheduler.py +296 -0
  79. aipt_v2/orchestrator.py +2284 -0
  80. aipt_v2/payloads/__init__.py +27 -0
  81. aipt_v2/payloads/cmdi.py +150 -0
  82. aipt_v2/payloads/sqli.py +263 -0
  83. aipt_v2/payloads/ssrf.py +204 -0
  84. aipt_v2/payloads/templates.py +222 -0
  85. aipt_v2/payloads/traversal.py +166 -0
  86. aipt_v2/payloads/xss.py +204 -0
  87. aipt_v2/prompts/__init__.py +60 -0
  88. aipt_v2/proxy/__init__.py +29 -0
  89. aipt_v2/proxy/history.py +352 -0
  90. aipt_v2/proxy/interceptor.py +452 -0
  91. aipt_v2/recon/__init__.py +44 -0
  92. aipt_v2/recon/dns.py +241 -0
  93. aipt_v2/recon/osint.py +367 -0
  94. aipt_v2/recon/subdomain.py +372 -0
  95. aipt_v2/recon/tech_detect.py +311 -0
  96. aipt_v2/reports/__init__.py +17 -0
  97. aipt_v2/reports/generator.py +313 -0
  98. aipt_v2/reports/html_report.py +378 -0
  99. aipt_v2/runtime/__init__.py +44 -0
  100. aipt_v2/runtime/base.py +30 -0
  101. aipt_v2/runtime/docker.py +401 -0
  102. aipt_v2/runtime/local.py +346 -0
  103. aipt_v2/runtime/tool_server.py +205 -0
  104. aipt_v2/scanners/__init__.py +28 -0
  105. aipt_v2/scanners/base.py +273 -0
  106. aipt_v2/scanners/nikto.py +244 -0
  107. aipt_v2/scanners/nmap.py +402 -0
  108. aipt_v2/scanners/nuclei.py +273 -0
  109. aipt_v2/scanners/web.py +454 -0
  110. aipt_v2/scripts/security_audit.py +366 -0
  111. aipt_v2/telemetry/__init__.py +7 -0
  112. aipt_v2/telemetry/tracer.py +347 -0
  113. aipt_v2/terminal/__init__.py +28 -0
  114. aipt_v2/terminal/executor.py +400 -0
  115. aipt_v2/terminal/sandbox.py +350 -0
  116. aipt_v2/tools/__init__.py +44 -0
  117. aipt_v2/tools/active_directory/__init__.py +78 -0
  118. aipt_v2/tools/active_directory/ad_config.py +238 -0
  119. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  120. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  121. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  122. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  123. aipt_v2/tools/agents_graph/__init__.py +19 -0
  124. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  125. aipt_v2/tools/api_security/__init__.py +76 -0
  126. aipt_v2/tools/api_security/api_discovery.py +608 -0
  127. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  128. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  129. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  130. aipt_v2/tools/browser/__init__.py +5 -0
  131. aipt_v2/tools/browser/browser_actions.py +238 -0
  132. aipt_v2/tools/browser/browser_instance.py +535 -0
  133. aipt_v2/tools/browser/tab_manager.py +344 -0
  134. aipt_v2/tools/cloud/__init__.py +70 -0
  135. aipt_v2/tools/cloud/cloud_config.py +273 -0
  136. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  137. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  138. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  139. aipt_v2/tools/executor.py +307 -0
  140. aipt_v2/tools/parser.py +408 -0
  141. aipt_v2/tools/proxy/__init__.py +5 -0
  142. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  143. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  144. aipt_v2/tools/registry.py +196 -0
  145. aipt_v2/tools/scanners/__init__.py +343 -0
  146. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  147. aipt_v2/tools/scanners/burp_tool.py +631 -0
  148. aipt_v2/tools/scanners/config.py +156 -0
  149. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  150. aipt_v2/tools/scanners/zap_tool.py +612 -0
  151. aipt_v2/tools/terminal/__init__.py +5 -0
  152. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  153. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  154. aipt_v2/tools/terminal/terminal_session.py +449 -0
  155. aipt_v2/tools/tool_processing.py +108 -0
  156. aipt_v2/utils/__init__.py +17 -0
  157. aipt_v2/utils/logging.py +201 -0
  158. aipt_v2/utils/model_manager.py +187 -0
  159. aipt_v2/utils/searchers/__init__.py +269 -0
  160. aiptx-2.0.2.dist-info/METADATA +324 -0
  161. aiptx-2.0.2.dist-info/RECORD +165 -0
  162. aiptx-2.0.2.dist-info/WHEEL +5 -0
  163. aiptx-2.0.2.dist-info/entry_points.txt +7 -0
  164. aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
  165. aiptx-2.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,577 @@
1
+ """
2
+ JWT Token Security Analyzer
3
+
4
+ Comprehensive JWT (JSON Web Token) security testing:
5
+ - Algorithm confusion attacks (none, HS256/RS256 switch)
6
+ - Signature verification bypass
7
+ - Key brute-forcing (weak secrets)
8
+ - Claim manipulation (exp, iat, aud, iss)
9
+ - Token structure analysis
10
+ - JWK/JWKS endpoint testing
11
+
12
+ References:
13
+ - https://portswigger.net/web-security/jwt
14
+ - https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
15
+
16
+ Usage:
17
+ from aipt_v2.tools.api_security import JWTAnalyzer
18
+
19
+ analyzer = JWTAnalyzer()
20
+ findings = analyzer.analyze("eyJhbGciOiJIUzI1NiIs...")
21
+ """
22
+
23
+ import base64
24
+ import hashlib
25
+ import hmac
26
+ import json
27
+ import re
28
+ from dataclasses import dataclass, field
29
+ from datetime import datetime, timezone
30
+ from typing import List, Dict, Any, Optional, Tuple
31
+
32
+ try:
33
+ import jwt as pyjwt
34
+ HAS_PYJWT = True
35
+ except ImportError:
36
+ HAS_PYJWT = False
37
+
38
+
39
+ @dataclass
40
+ class JWTFinding:
41
+ """JWT security finding."""
42
+ vulnerability: str
43
+ severity: str # critical, high, medium, low, info
44
+ description: str
45
+ evidence: str
46
+ remediation: str
47
+ affected_claim: str = ""
48
+ attack_vector: str = ""
49
+ timestamp: str = ""
50
+ cwe: str = ""
51
+
52
+ def __post_init__(self):
53
+ if not self.timestamp:
54
+ self.timestamp = datetime.now(timezone.utc).isoformat()
55
+
56
+
57
+ @dataclass
58
+ class JWTInfo:
59
+ """Parsed JWT information."""
60
+ raw: str
61
+ header: Dict[str, Any]
62
+ payload: Dict[str, Any]
63
+ signature: str
64
+ algorithm: str
65
+ is_valid: bool = False
66
+ expiration: Optional[datetime] = None
67
+ issued_at: Optional[datetime] = None
68
+ not_before: Optional[datetime] = None
69
+
70
+
71
+ class JWTAnalyzer:
72
+ """
73
+ JWT Security Analyzer.
74
+
75
+ Analyzes JWT tokens for security vulnerabilities
76
+ including algorithm confusion, weak signatures,
77
+ and claim manipulation issues.
78
+ """
79
+
80
+ # Common weak secrets for brute-force
81
+ COMMON_SECRETS = [
82
+ "secret", "password", "123456", "admin", "key",
83
+ "jwt_secret", "jwt-secret", "token", "auth",
84
+ "supersecret", "changeme", "default", "test",
85
+ "development", "dev", "production", "prod",
86
+ "your-256-bit-secret", "your-secret-key",
87
+ "HS256-secret", "secret123", "secretkey",
88
+ "application-secret", "app-secret", "api-secret"
89
+ ]
90
+
91
+ # Extended wordlist
92
+ EXTENDED_SECRETS = COMMON_SECRETS + [
93
+ # Company-style secrets
94
+ "company-secret", "my-secret-key", "very-secret",
95
+ # Lazy admin secrets
96
+ "password123", "admin123", "root", "toor",
97
+ # Framework defaults
98
+ "django-insecure", "flask-secret", "express-secret",
99
+ "rails-secret", "laravel-secret",
100
+ # Environment-style
101
+ "JWT_SECRET", "API_SECRET", "AUTH_SECRET",
102
+ # UUID-like
103
+ "00000000-0000-0000-0000-000000000000",
104
+ # Simple variations
105
+ "Secret", "SECRET", "Password", "PASSWORD"
106
+ ]
107
+
108
+ def __init__(self, extended_wordlist: bool = False):
109
+ """
110
+ Initialize JWT analyzer.
111
+
112
+ Args:
113
+ extended_wordlist: Use extended wordlist for brute-force
114
+ """
115
+ self.secrets = self.EXTENDED_SECRETS if extended_wordlist else self.COMMON_SECRETS
116
+ self.findings: List[JWTFinding] = []
117
+
118
+ def _base64_decode(self, data: str) -> bytes:
119
+ """Decode base64url encoded data."""
120
+ # Add padding if needed
121
+ padding = 4 - len(data) % 4
122
+ if padding != 4:
123
+ data += "=" * padding
124
+ # Replace URL-safe characters
125
+ data = data.replace("-", "+").replace("_", "/")
126
+ return base64.b64decode(data)
127
+
128
+ def _base64_encode(self, data: bytes) -> str:
129
+ """Encode data as base64url."""
130
+ encoded = base64.b64encode(data).decode()
131
+ # Make URL-safe
132
+ return encoded.replace("+", "-").replace("/", "_").rstrip("=")
133
+
134
+ def parse_token(self, token: str) -> Optional[JWTInfo]:
135
+ """Parse JWT token into components."""
136
+ parts = token.split(".")
137
+
138
+ if len(parts) != 3:
139
+ return None
140
+
141
+ try:
142
+ header_json = self._base64_decode(parts[0])
143
+ payload_json = self._base64_decode(parts[1])
144
+
145
+ header = json.loads(header_json)
146
+ payload = json.loads(payload_json)
147
+
148
+ # Parse timestamps
149
+ expiration = None
150
+ issued_at = None
151
+ not_before = None
152
+
153
+ if "exp" in payload:
154
+ try:
155
+ expiration = datetime.fromtimestamp(payload["exp"], tz=timezone.utc)
156
+ except (ValueError, OSError):
157
+ pass
158
+
159
+ if "iat" in payload:
160
+ try:
161
+ issued_at = datetime.fromtimestamp(payload["iat"], tz=timezone.utc)
162
+ except (ValueError, OSError):
163
+ pass
164
+
165
+ if "nbf" in payload:
166
+ try:
167
+ not_before = datetime.fromtimestamp(payload["nbf"], tz=timezone.utc)
168
+ except (ValueError, OSError):
169
+ pass
170
+
171
+ return JWTInfo(
172
+ raw=token,
173
+ header=header,
174
+ payload=payload,
175
+ signature=parts[2],
176
+ algorithm=header.get("alg", "unknown"),
177
+ expiration=expiration,
178
+ issued_at=issued_at,
179
+ not_before=not_before
180
+ )
181
+
182
+ except Exception:
183
+ return None
184
+
185
+ def test_none_algorithm(self, jwt_info: JWTInfo) -> List[JWTFinding]:
186
+ """Test for 'none' algorithm vulnerability."""
187
+ findings = []
188
+
189
+ # Create token with "none" algorithm
190
+ none_header = {"alg": "none", "typ": "JWT"}
191
+ none_header_b64 = self._base64_encode(json.dumps(none_header).encode())
192
+ payload_b64 = jwt_info.raw.split(".")[1]
193
+
194
+ # Tokens without signature
195
+ none_tokens = [
196
+ f"{none_header_b64}.{payload_b64}.",
197
+ f"{none_header_b64}.{payload_b64}",
198
+ ]
199
+
200
+ # Also try "None", "NONE", "nOnE"
201
+ for alg_variant in ["None", "NONE", "nOnE"]:
202
+ variant_header = {"alg": alg_variant, "typ": "JWT"}
203
+ variant_b64 = self._base64_encode(json.dumps(variant_header).encode())
204
+ none_tokens.append(f"{variant_b64}.{payload_b64}.")
205
+
206
+ findings.append(JWTFinding(
207
+ vulnerability="None Algorithm Attack Vector",
208
+ severity="critical",
209
+ description="JWT library may accept 'none' algorithm, allowing signature bypass",
210
+ evidence=f"Generated attack tokens with none algorithm",
211
+ attack_vector=none_tokens[0],
212
+ remediation="Explicitly verify algorithm in JWT validation. Never accept 'none'.",
213
+ cwe="CWE-327"
214
+ ))
215
+
216
+ return findings
217
+
218
+ def test_algorithm_confusion(self, jwt_info: JWTInfo) -> List[JWTFinding]:
219
+ """Test for algorithm confusion (HS256/RS256 switch)."""
220
+ findings = []
221
+
222
+ # If token uses RS256/RS384/RS512, could be vulnerable to HS256 confusion
223
+ if jwt_info.algorithm in ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512"]:
224
+ findings.append(JWTFinding(
225
+ vulnerability="Potential Algorithm Confusion",
226
+ severity="high",
227
+ description=f"Token uses {jwt_info.algorithm}. If the public key is known, "
228
+ "attacker may forge tokens by switching to HS256 and using public key as secret.",
229
+ evidence=f"Current algorithm: {jwt_info.algorithm}",
230
+ attack_vector="Switch alg to HS256 and sign with public key",
231
+ remediation="Explicitly specify expected algorithm during verification. "
232
+ "Never allow algorithm to be changed by the token itself.",
233
+ cwe="CWE-327"
234
+ ))
235
+
236
+ return findings
237
+
238
+ def test_weak_secret(self, jwt_info: JWTInfo) -> List[JWTFinding]:
239
+ """Test for weak/common secrets using brute-force."""
240
+ findings = []
241
+
242
+ if jwt_info.algorithm not in ["HS256", "HS384", "HS512"]:
243
+ return findings
244
+
245
+ # Get algorithm details
246
+ alg_map = {
247
+ "HS256": ("sha256", 256),
248
+ "HS384": ("sha384", 384),
249
+ "HS512": ("sha512", 512)
250
+ }
251
+
252
+ hash_alg, _ = alg_map.get(jwt_info.algorithm, ("sha256", 256))
253
+
254
+ # Get message to sign
255
+ parts = jwt_info.raw.split(".")
256
+ message = f"{parts[0]}.{parts[1]}".encode()
257
+ target_sig = parts[2]
258
+
259
+ # Try common secrets
260
+ cracked_secret = None
261
+ for secret in self.secrets:
262
+ # Compute signature
263
+ sig = hmac.new(
264
+ secret.encode(),
265
+ message,
266
+ hash_alg
267
+ ).digest()
268
+ computed_sig = self._base64_encode(sig)
269
+
270
+ if computed_sig == target_sig:
271
+ cracked_secret = secret
272
+ break
273
+
274
+ if cracked_secret:
275
+ findings.append(JWTFinding(
276
+ vulnerability="Weak JWT Secret",
277
+ severity="critical",
278
+ description=f"JWT secret is a common/weak value: '{cracked_secret}'",
279
+ evidence=f"Secret cracked: {cracked_secret}",
280
+ attack_vector=f"Use secret '{cracked_secret}' to forge tokens",
281
+ remediation="Use a strong, random secret (minimum 256 bits). "
282
+ "Consider using asymmetric algorithms (RS256).",
283
+ cwe="CWE-521"
284
+ ))
285
+ else:
286
+ findings.append(JWTFinding(
287
+ vulnerability="JWT Secret Brute-Force Test",
288
+ severity="info",
289
+ description=f"Tested {len(self.secrets)} common secrets - none matched",
290
+ evidence="Secret not in common wordlist",
291
+ remediation="Continue using a strong secret",
292
+ cwe=""
293
+ ))
294
+
295
+ return findings
296
+
297
+ def test_expiration(self, jwt_info: JWTInfo) -> List[JWTFinding]:
298
+ """Test token expiration claims."""
299
+ findings = []
300
+ now = datetime.now(timezone.utc)
301
+
302
+ # Check for missing expiration
303
+ if not jwt_info.expiration:
304
+ if "exp" not in jwt_info.payload:
305
+ findings.append(JWTFinding(
306
+ vulnerability="Missing Token Expiration",
307
+ severity="high",
308
+ description="Token has no expiration claim (exp)",
309
+ evidence="No 'exp' claim in payload",
310
+ affected_claim="exp",
311
+ remediation="Always include expiration in tokens. Use short lifetimes.",
312
+ cwe="CWE-613"
313
+ ))
314
+ else:
315
+ # Check if expired
316
+ if jwt_info.expiration < now:
317
+ findings.append(JWTFinding(
318
+ vulnerability="Expired Token",
319
+ severity="info",
320
+ description=f"Token expired at {jwt_info.expiration.isoformat()}",
321
+ evidence=f"exp: {jwt_info.payload.get('exp')}",
322
+ affected_claim="exp",
323
+ remediation="Refresh token or obtain new one",
324
+ cwe=""
325
+ ))
326
+ else:
327
+ # Check for excessively long expiration
328
+ time_until_exp = (jwt_info.expiration - now).total_seconds()
329
+ if time_until_exp > 86400 * 30: # More than 30 days
330
+ findings.append(JWTFinding(
331
+ vulnerability="Long Token Lifetime",
332
+ severity="medium",
333
+ description=f"Token valid for {time_until_exp / 86400:.1f} days",
334
+ evidence=f"exp: {jwt_info.payload.get('exp')}",
335
+ affected_claim="exp",
336
+ remediation="Use shorter token lifetimes. Implement refresh tokens.",
337
+ cwe="CWE-613"
338
+ ))
339
+
340
+ return findings
341
+
342
+ def test_sensitive_claims(self, jwt_info: JWTInfo) -> List[JWTFinding]:
343
+ """Check for sensitive data in claims."""
344
+ findings = []
345
+
346
+ sensitive_patterns = {
347
+ "password": r"(password|passwd|pwd)",
348
+ "secret": r"(secret|api_key|apikey|private)",
349
+ "credit_card": r"(\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4})",
350
+ "ssn": r"\d{3}-\d{2}-\d{4}",
351
+ "email": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
352
+ }
353
+
354
+ payload_str = json.dumps(jwt_info.payload).lower()
355
+
356
+ for data_type, pattern in sensitive_patterns.items():
357
+ if data_type in ["password", "secret"]:
358
+ # Check for key names
359
+ for key in jwt_info.payload.keys():
360
+ if re.search(pattern, key.lower()):
361
+ findings.append(JWTFinding(
362
+ vulnerability="Sensitive Data in JWT",
363
+ severity="high",
364
+ description=f"Token contains potentially sensitive claim: {key}",
365
+ evidence=f"Claim name matches pattern: {data_type}",
366
+ affected_claim=key,
367
+ remediation="Never store sensitive data in JWT. "
368
+ "Store server-side and reference by ID.",
369
+ cwe="CWE-200"
370
+ ))
371
+ else:
372
+ # Check for patterns in values
373
+ if re.search(pattern, payload_str):
374
+ findings.append(JWTFinding(
375
+ vulnerability=f"Potential {data_type.replace('_', ' ').title()} in JWT",
376
+ severity="medium",
377
+ description=f"Token may contain {data_type} data",
378
+ evidence=f"Pattern match for {data_type}",
379
+ remediation="Review and remove sensitive data from token",
380
+ cwe="CWE-200"
381
+ ))
382
+
383
+ return findings
384
+
385
+ def test_kid_injection(self, jwt_info: JWTInfo) -> List[JWTFinding]:
386
+ """Test for kid (Key ID) injection vulnerabilities."""
387
+ findings = []
388
+
389
+ kid = jwt_info.header.get("kid")
390
+
391
+ if kid:
392
+ # Check for potential SQL injection in kid
393
+ sql_patterns = ["'", '"', ";", "--", "/*", "*/", "OR", "AND"]
394
+ for pattern in sql_patterns:
395
+ if pattern in kid:
396
+ findings.append(JWTFinding(
397
+ vulnerability="Potential SQL Injection in kid",
398
+ severity="critical",
399
+ description="kid header may be vulnerable to SQL injection",
400
+ evidence=f"kid contains: {pattern}",
401
+ attack_vector=f"kid: {kid}",
402
+ remediation="Validate and sanitize kid before use in queries",
403
+ cwe="CWE-89"
404
+ ))
405
+ break
406
+
407
+ # Check for path traversal
408
+ if ".." in kid or "/" in kid or "\\" in kid:
409
+ findings.append(JWTFinding(
410
+ vulnerability="Potential Path Traversal in kid",
411
+ severity="high",
412
+ description="kid header may allow path traversal",
413
+ evidence=f"kid: {kid}",
414
+ attack_vector=f"kid: ../../path/to/key",
415
+ remediation="Validate kid against allowlist of key identifiers",
416
+ cwe="CWE-22"
417
+ ))
418
+
419
+ # Check for command injection
420
+ cmd_patterns = ["|", "`", "$", "(", ")", ";"]
421
+ for pattern in cmd_patterns:
422
+ if pattern in kid:
423
+ findings.append(JWTFinding(
424
+ vulnerability="Potential Command Injection in kid",
425
+ severity="critical",
426
+ description="kid header may allow command injection",
427
+ evidence=f"kid contains: {pattern}",
428
+ attack_vector=f"kid: {kid}",
429
+ remediation="Never use kid in shell commands",
430
+ cwe="CWE-78"
431
+ ))
432
+ break
433
+
434
+ return findings
435
+
436
+ def test_jku_jwks_uri(self, jwt_info: JWTInfo) -> List[JWTFinding]:
437
+ """Test for jku/jwks_uri header injection."""
438
+ findings = []
439
+
440
+ jku = jwt_info.header.get("jku")
441
+ x5u = jwt_info.header.get("x5u")
442
+
443
+ for header_name, value in [("jku", jku), ("x5u", x5u)]:
444
+ if value:
445
+ findings.append(JWTFinding(
446
+ vulnerability=f"External Key Reference ({header_name})",
447
+ severity="high",
448
+ description=f"Token references external key via {header_name}: {value}",
449
+ evidence=f"{header_name}: {value}",
450
+ attack_vector=f"Replace {header_name} with attacker-controlled URL",
451
+ remediation=f"Do not accept {header_name} from tokens. "
452
+ "Use pre-configured key locations.",
453
+ cwe="CWE-345"
454
+ ))
455
+
456
+ return findings
457
+
458
+ def generate_attack_tokens(self, jwt_info: JWTInfo) -> Dict[str, str]:
459
+ """Generate various attack tokens for testing."""
460
+ attacks = {}
461
+
462
+ payload_b64 = jwt_info.raw.split(".")[1]
463
+
464
+ # None algorithm attack
465
+ none_header = self._base64_encode(json.dumps({"alg": "none", "typ": "JWT"}).encode())
466
+ attacks["none_algorithm"] = f"{none_header}.{payload_b64}."
467
+
468
+ # Modified payload (admin escalation)
469
+ modified_payload = jwt_info.payload.copy()
470
+ modified_payload["admin"] = True
471
+ modified_payload["role"] = "admin"
472
+ if "sub" in modified_payload:
473
+ modified_payload["sub"] = "admin"
474
+
475
+ mod_payload_b64 = self._base64_encode(json.dumps(modified_payload).encode())
476
+ attacks["admin_escalation_unsigned"] = f"{none_header}.{mod_payload_b64}."
477
+
478
+ # Expired timestamp manipulation
479
+ no_exp_payload = jwt_info.payload.copy()
480
+ no_exp_payload.pop("exp", None)
481
+ no_exp_b64 = self._base64_encode(json.dumps(no_exp_payload).encode())
482
+ attacks["no_expiration"] = f"{none_header}.{no_exp_b64}."
483
+
484
+ return attacks
485
+
486
+ def analyze(self, token: str) -> Tuple[JWTInfo, List[JWTFinding]]:
487
+ """
488
+ Perform full JWT analysis.
489
+
490
+ Args:
491
+ token: JWT token string
492
+
493
+ Returns:
494
+ Tuple of (JWTInfo, List of findings)
495
+ """
496
+ findings = []
497
+
498
+ # Parse token
499
+ jwt_info = self.parse_token(token)
500
+
501
+ if not jwt_info:
502
+ findings.append(JWTFinding(
503
+ vulnerability="Invalid JWT Format",
504
+ severity="info",
505
+ description="Token is not a valid JWT format",
506
+ evidence=f"Token: {token[:50]}...",
507
+ remediation="Ensure token has three base64url-encoded parts separated by dots"
508
+ ))
509
+ return JWTInfo(
510
+ raw=token,
511
+ header={},
512
+ payload={},
513
+ signature="",
514
+ algorithm="unknown"
515
+ ), findings
516
+
517
+ # Run all tests
518
+ findings.extend(self.test_none_algorithm(jwt_info))
519
+ findings.extend(self.test_algorithm_confusion(jwt_info))
520
+ findings.extend(self.test_weak_secret(jwt_info))
521
+ findings.extend(self.test_expiration(jwt_info))
522
+ findings.extend(self.test_sensitive_claims(jwt_info))
523
+ findings.extend(self.test_kid_injection(jwt_info))
524
+ findings.extend(self.test_jku_jwks_uri(jwt_info))
525
+
526
+ return jwt_info, findings
527
+
528
+ def get_summary(self, findings: List[JWTFinding]) -> Dict[str, Any]:
529
+ """Get summary of findings."""
530
+ return {
531
+ "total": len(findings),
532
+ "critical": len([f for f in findings if f.severity == "critical"]),
533
+ "high": len([f for f in findings if f.severity == "high"]),
534
+ "medium": len([f for f in findings if f.severity == "medium"]),
535
+ "low": len([f for f in findings if f.severity == "low"]),
536
+ "info": len([f for f in findings if f.severity == "info"])
537
+ }
538
+
539
+
540
+ # Convenience function
541
+ def analyze_jwt(token: str, extended_wordlist: bool = False) -> Tuple[JWTInfo, List[JWTFinding]]:
542
+ """
543
+ Quick JWT analysis.
544
+
545
+ Args:
546
+ token: JWT token string
547
+ extended_wordlist: Use extended secret wordlist
548
+
549
+ Returns:
550
+ Tuple of (JWTInfo, List of findings)
551
+ """
552
+ analyzer = JWTAnalyzer(extended_wordlist=extended_wordlist)
553
+ return analyzer.analyze(token)
554
+
555
+
556
+ def decode_jwt(token: str) -> Dict[str, Any]:
557
+ """
558
+ Decode JWT without verification (for inspection).
559
+
560
+ Args:
561
+ token: JWT token string
562
+
563
+ Returns:
564
+ Dict with header and payload
565
+ """
566
+ analyzer = JWTAnalyzer()
567
+ jwt_info = analyzer.parse_token(token)
568
+
569
+ if jwt_info:
570
+ return {
571
+ "header": jwt_info.header,
572
+ "payload": jwt_info.payload,
573
+ "algorithm": jwt_info.algorithm,
574
+ "expiration": jwt_info.expiration.isoformat() if jwt_info.expiration else None,
575
+ "issued_at": jwt_info.issued_at.isoformat() if jwt_info.issued_at else None
576
+ }
577
+ return {}