@intentsolutionsio/penetration-tester 2.0.0 → 3.0.4
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.
- package/.claude-plugin/plugin.json +8 -3
- package/README.md +8 -0
- package/commands/pentest.md +5 -0
- package/package.json +8 -3
- package/skills/analyzing-tls-config/SKILL.md +221 -0
- package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
- package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
- package/skills/analyzing-tls-config/references/THEORY.md +128 -0
- package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
- package/skills/auditing-cors-policy/SKILL.md +186 -0
- package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
- package/skills/auditing-cors-policy/references/THEORY.md +142 -0
- package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
- package/skills/auditing-npm-dependencies/SKILL.md +254 -0
- package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
- package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
- package/skills/auditing-python-dependencies/SKILL.md +251 -0
- package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
- package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
- package/skills/checking-http-security-headers/SKILL.md +176 -0
- package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
- package/skills/checking-http-security-headers/references/THEORY.md +137 -0
- package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
- package/skills/checking-license-compliance/SKILL.md +225 -0
- package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
- package/skills/checking-license-compliance/references/THEORY.md +152 -0
- package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
- package/skills/composing-vulnerability-report/SKILL.md +212 -0
- package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
- package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
- package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
- package/skills/confirming-pentest-authorization/SKILL.md +247 -0
- package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
- package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
- package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
- package/skills/defining-pentest-scope/SKILL.md +227 -0
- package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
- package/skills/defining-pentest-scope/references/THEORY.md +170 -0
- package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
- package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
- package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
- package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
- package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
- package/skills/detecting-debug-endpoints/SKILL.md +207 -0
- package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
- package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
- package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
- package/skills/detecting-directory-listing/SKILL.md +206 -0
- package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
- package/skills/detecting-directory-listing/references/THEORY.md +203 -0
- package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
- package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
- package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
- package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
- package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
- package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
- package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
- package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
- package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
- package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
- package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
- package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
- package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
- package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
- package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
- package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
- package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
- package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
- package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
- package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
- package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
- package/skills/detecting-weak-cryptography/SKILL.md +147 -0
- package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
- package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
- package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
- package/skills/fingerprinting-server-software/SKILL.md +191 -0
- package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
- package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
- package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
- package/skills/generating-executive-summary/SKILL.md +261 -0
- package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
- package/skills/generating-executive-summary/references/THEORY.md +195 -0
- package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
- package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
- package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
- package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
- package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
- package/skills/performing-penetration-testing/SKILL.md +282 -190
- package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
- package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
- package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
- package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
- package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
- package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
- package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
- package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
- package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
- package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
- package/skills/recording-pentest-engagement/SKILL.md +253 -0
- package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
- package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
- package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
- package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
- package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
- package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
- package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
- package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
- package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
- package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
- package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Static-analysis scan for weak cryptography usage.
|
|
3
|
+
|
|
4
|
+
References:
|
|
5
|
+
CWE-327 Use of a Broken or Risky Cryptographic Algorithm
|
|
6
|
+
CWE-330 Use of Insufficiently Random Values
|
|
7
|
+
CWE-329 Not Using a Random IV with CBC Mode
|
|
8
|
+
CWE-295 Improper Certificate Validation
|
|
9
|
+
CWE-916 Use of Password Hash With Insufficient Computational Effort
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
_PLUGIN_ROOT = Path(__file__).resolve().parents[3]
|
|
20
|
+
if str(_PLUGIN_ROOT) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(_PLUGIN_ROOT))
|
|
22
|
+
|
|
23
|
+
from lib.finding import Finding, Severity # noqa: E402
|
|
24
|
+
from lib.report import emit, exit_code # noqa: E402
|
|
25
|
+
|
|
26
|
+
SKILL_ID = "detecting-weak-cryptography"
|
|
27
|
+
|
|
28
|
+
PY_PATTERNS = [
|
|
29
|
+
("Python hashlib.md5 (collision-broken)", Severity.HIGH, "CWE-327", r"\bhashlib\.md5\s*\(", "python"),
|
|
30
|
+
("Python hashlib.sha1 (collision-broken)", Severity.HIGH, "CWE-327", r"\bhashlib\.sha1\s*\(", "python"),
|
|
31
|
+
(
|
|
32
|
+
"Python random.random for crypto context",
|
|
33
|
+
Severity.CRITICAL,
|
|
34
|
+
"CWE-330",
|
|
35
|
+
r"\brandom\.(?:random|randint|choice|sample)\s*\([^)]*\)[^=\n]*(?:key|token|secret|password|nonce|iv|salt)",
|
|
36
|
+
"python",
|
|
37
|
+
),
|
|
38
|
+
("Python ssl verify=False", Severity.CRITICAL, "CWE-295", r"\bverify\s*=\s*False\b", "python"),
|
|
39
|
+
("Python urllib3.disable_warnings", Severity.HIGH, "CWE-295", r"\burllib3\.disable_warnings\s*\(", "python"),
|
|
40
|
+
(
|
|
41
|
+
"Python requests.get with verify=False",
|
|
42
|
+
Severity.CRITICAL,
|
|
43
|
+
"CWE-295",
|
|
44
|
+
r"requests\.(?:get|post|put|delete|patch|head|options|request)\s*\([^)]*verify\s*=\s*False",
|
|
45
|
+
"python",
|
|
46
|
+
),
|
|
47
|
+
("Python Cryptodome ECB mode", Severity.CRITICAL, "CWE-327", r"MODE_ECB", "python"),
|
|
48
|
+
(
|
|
49
|
+
"Python hashlib.sha256 for password (no KDF)",
|
|
50
|
+
Severity.HIGH,
|
|
51
|
+
"CWE-916",
|
|
52
|
+
r"\bhashlib\.sha256\s*\([^)]*password",
|
|
53
|
+
"python",
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
"Python Cryptodome DES / 3DES",
|
|
57
|
+
Severity.CRITICAL,
|
|
58
|
+
"CWE-327",
|
|
59
|
+
r"from\s+Cryptodome\.Cipher\s+import\s+DES|from\s+Crypto\.Cipher\s+import\s+DES",
|
|
60
|
+
"python",
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
JS_PATTERNS = [
|
|
64
|
+
("JS crypto.createHash('md5')", Severity.HIGH, "CWE-327", r"createHash\s*\(\s*['\"]md5['\"]\s*\)", "javascript"),
|
|
65
|
+
("JS crypto.createHash('sha1')", Severity.HIGH, "CWE-327", r"createHash\s*\(\s*['\"]sha1['\"]\s*\)", "javascript"),
|
|
66
|
+
(
|
|
67
|
+
"JS Math.random for crypto purposes",
|
|
68
|
+
Severity.CRITICAL,
|
|
69
|
+
"CWE-330",
|
|
70
|
+
r"Math\.random\s*\(\s*\)[^=\n]*(?:key|token|secret|password|nonce|iv|salt|sessionId)",
|
|
71
|
+
"javascript",
|
|
72
|
+
),
|
|
73
|
+
(
|
|
74
|
+
"JS axios with rejectUnauthorized:false",
|
|
75
|
+
Severity.CRITICAL,
|
|
76
|
+
"CWE-295",
|
|
77
|
+
r"rejectUnauthorized\s*:\s*false",
|
|
78
|
+
"javascript",
|
|
79
|
+
),
|
|
80
|
+
(
|
|
81
|
+
"JS Node tls with rejectUnauthorized:false in agent",
|
|
82
|
+
Severity.CRITICAL,
|
|
83
|
+
"CWE-295",
|
|
84
|
+
r"new\s+https?\.Agent\s*\(\s*\{[^}]*rejectUnauthorized\s*:\s*false",
|
|
85
|
+
"javascript",
|
|
86
|
+
),
|
|
87
|
+
(
|
|
88
|
+
"JS NODE_TLS_REJECT_UNAUTHORIZED env disable",
|
|
89
|
+
Severity.CRITICAL,
|
|
90
|
+
"CWE-295",
|
|
91
|
+
r"NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['\"]?0",
|
|
92
|
+
"javascript",
|
|
93
|
+
),
|
|
94
|
+
(
|
|
95
|
+
"JS Cipher with 'des' / 'des-ede' / 'rc4'",
|
|
96
|
+
Severity.CRITICAL,
|
|
97
|
+
"CWE-327",
|
|
98
|
+
r"createCipheriv?\s*\(\s*['\"](?:des|3des|des-ede|des-ede3|rc4|arc4|bf|cast)['\"]",
|
|
99
|
+
"javascript",
|
|
100
|
+
),
|
|
101
|
+
(
|
|
102
|
+
"JS Cipher with ECB mode",
|
|
103
|
+
Severity.CRITICAL,
|
|
104
|
+
"CWE-327",
|
|
105
|
+
r"createCipheriv?\s*\(\s*['\"][a-z0-9-]+-ecb['\"]",
|
|
106
|
+
"javascript",
|
|
107
|
+
),
|
|
108
|
+
]
|
|
109
|
+
JAVA_PATTERNS = [
|
|
110
|
+
("Java MessageDigest MD5", Severity.HIGH, "CWE-327", r'MessageDigest\.getInstance\s*\(\s*"MD5"', "java"),
|
|
111
|
+
("Java MessageDigest SHA-1", Severity.HIGH, "CWE-327", r'MessageDigest\.getInstance\s*\(\s*"SHA-?1"', "java"),
|
|
112
|
+
(
|
|
113
|
+
"Java Cipher DES / DESede",
|
|
114
|
+
Severity.CRITICAL,
|
|
115
|
+
"CWE-327",
|
|
116
|
+
r'Cipher\.getInstance\s*\(\s*"(?:DES|DESede)(?:/|")',
|
|
117
|
+
"java",
|
|
118
|
+
),
|
|
119
|
+
("Java Cipher RC4", Severity.CRITICAL, "CWE-327", r'Cipher\.getInstance\s*\(\s*"(?:RC4|ARCFOUR)', "java"),
|
|
120
|
+
("Java Cipher ECB mode", Severity.CRITICAL, "CWE-327", r'Cipher\.getInstance\s*\(\s*"[^/]+/ECB/', "java"),
|
|
121
|
+
(
|
|
122
|
+
"Java new Random() (use SecureRandom for crypto)",
|
|
123
|
+
Severity.HIGH,
|
|
124
|
+
"CWE-330",
|
|
125
|
+
r"new\s+java\.util\.Random\s*\(",
|
|
126
|
+
"java",
|
|
127
|
+
),
|
|
128
|
+
(
|
|
129
|
+
"Java X509TrustManager always-true",
|
|
130
|
+
Severity.CRITICAL,
|
|
131
|
+
"CWE-295",
|
|
132
|
+
r"public\s+void\s+check(?:Server|Client)Trusted\s*\([^)]*\)\s*\{\s*\}",
|
|
133
|
+
"java",
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
"Java HostnameVerifier always-true",
|
|
137
|
+
Severity.CRITICAL,
|
|
138
|
+
"CWE-295",
|
|
139
|
+
r"public\s+boolean\s+verify\s*\([^)]*\)\s*\{\s*return\s+true",
|
|
140
|
+
"java",
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
GO_PATTERNS = [
|
|
144
|
+
("Go md5.New() for security", Severity.HIGH, "CWE-327", r"\bmd5\.New\s*\(\s*\)", "go"),
|
|
145
|
+
("Go sha1.New() for security", Severity.HIGH, "CWE-327", r"\bsha1\.New\s*\(\s*\)", "go"),
|
|
146
|
+
("Go InsecureSkipVerify: true", Severity.CRITICAL, "CWE-295", r"InsecureSkipVerify\s*:\s*true", "go"),
|
|
147
|
+
("Go DES cipher", Severity.CRITICAL, "CWE-327", r"des\.(?:NewCipher|NewTripleDESCipher)\s*\(", "go"),
|
|
148
|
+
("Go RC4 cipher", Severity.CRITICAL, "CWE-327", r"rc4\.NewCipher\s*\(", "go"),
|
|
149
|
+
("Go math/rand for crypto context", Severity.CRITICAL, "CWE-330", r'"math/rand"', "go"),
|
|
150
|
+
]
|
|
151
|
+
PHP_PATTERNS = [
|
|
152
|
+
(
|
|
153
|
+
"PHP md5 / sha1 for password",
|
|
154
|
+
Severity.HIGH,
|
|
155
|
+
"CWE-916",
|
|
156
|
+
r"\b(?:md5|sha1)\s*\(\s*\$(?:password|passwd|pwd)",
|
|
157
|
+
"php",
|
|
158
|
+
),
|
|
159
|
+
(
|
|
160
|
+
"PHP DES / 3DES cipher",
|
|
161
|
+
Severity.CRITICAL,
|
|
162
|
+
"CWE-327",
|
|
163
|
+
r"openssl_encrypt\s*\([^,]+,\s*['\"](?:des|des-ede|des-ede3|rc4)",
|
|
164
|
+
"php",
|
|
165
|
+
),
|
|
166
|
+
(
|
|
167
|
+
"PHP ECB cipher mode",
|
|
168
|
+
Severity.CRITICAL,
|
|
169
|
+
"CWE-327",
|
|
170
|
+
r"openssl_encrypt\s*\([^,]+,\s*['\"][a-z0-9-]+-ecb['\"]",
|
|
171
|
+
"php",
|
|
172
|
+
),
|
|
173
|
+
(
|
|
174
|
+
"PHP curl CURLOPT_SSL_VERIFYPEER false",
|
|
175
|
+
Severity.CRITICAL,
|
|
176
|
+
"CWE-295",
|
|
177
|
+
r"CURLOPT_SSL_VERIFYPEER[^,)]*,\s*(?:false|0)\b",
|
|
178
|
+
"php",
|
|
179
|
+
),
|
|
180
|
+
(
|
|
181
|
+
"PHP rand() / mt_rand() for crypto",
|
|
182
|
+
Severity.CRITICAL,
|
|
183
|
+
"CWE-330",
|
|
184
|
+
r"\b(?:mt_)?rand\s*\([^)]*\)[^;\n]*(?:key|token|secret|password|nonce|salt)",
|
|
185
|
+
"php",
|
|
186
|
+
),
|
|
187
|
+
]
|
|
188
|
+
CSHARP_PATTERNS = [
|
|
189
|
+
("C# MD5CryptoServiceProvider", Severity.HIGH, "CWE-327", r"\bMD5(?:CryptoServiceProvider|Cng)?\b", "csharp"),
|
|
190
|
+
(
|
|
191
|
+
"C# SHA1CryptoServiceProvider",
|
|
192
|
+
Severity.HIGH,
|
|
193
|
+
"CWE-327",
|
|
194
|
+
r"\bSHA1(?:CryptoServiceProvider|Managed|Cng)?\b",
|
|
195
|
+
"csharp",
|
|
196
|
+
),
|
|
197
|
+
(
|
|
198
|
+
"C# DESCryptoServiceProvider",
|
|
199
|
+
Severity.CRITICAL,
|
|
200
|
+
"CWE-327",
|
|
201
|
+
r"\bDES(?:CryptoServiceProvider|TripleCryptoServiceProvider)?\b",
|
|
202
|
+
"csharp",
|
|
203
|
+
),
|
|
204
|
+
("C# RC4 (rare; via Rijndael in ECB)", Severity.CRITICAL, "CWE-327", r"\bRC4Managed\b", "csharp"),
|
|
205
|
+
("C# Cipher mode ECB", Severity.CRITICAL, "CWE-327", r"CipherMode\.ECB", "csharp"),
|
|
206
|
+
(
|
|
207
|
+
"C# ServerCertificateValidationCallback returning true",
|
|
208
|
+
Severity.CRITICAL,
|
|
209
|
+
"CWE-295",
|
|
210
|
+
r"ServicePointManager\.ServerCertificateValidationCallback\s*[=+]?=\s*(?:delegate|\([^)]*\)\s*=>\s*true)",
|
|
211
|
+
"csharp",
|
|
212
|
+
),
|
|
213
|
+
(
|
|
214
|
+
"C# new Random() for crypto",
|
|
215
|
+
Severity.HIGH,
|
|
216
|
+
"CWE-330",
|
|
217
|
+
r"new\s+Random\s*\(\s*\)\s*\.\s*(?:Next|NextBytes|NextDouble)\s*\([^)]*(?:key|token|secret|salt|nonce|iv)",
|
|
218
|
+
"csharp",
|
|
219
|
+
),
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
LANG_EXT_MAP = {
|
|
224
|
+
"python": {".py"},
|
|
225
|
+
"javascript": {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"},
|
|
226
|
+
"java": {".java", ".kt", ".scala"},
|
|
227
|
+
"go": {".go"},
|
|
228
|
+
"php": {".php"},
|
|
229
|
+
"csharp": {".cs"},
|
|
230
|
+
}
|
|
231
|
+
LANG_PATTERNS = {
|
|
232
|
+
"python": PY_PATTERNS,
|
|
233
|
+
"javascript": JS_PATTERNS,
|
|
234
|
+
"java": JAVA_PATTERNS,
|
|
235
|
+
"go": GO_PATTERNS,
|
|
236
|
+
"php": PHP_PATTERNS,
|
|
237
|
+
"csharp": CSHARP_PATTERNS,
|
|
238
|
+
}
|
|
239
|
+
SKIP_DIRS = {
|
|
240
|
+
"node_modules",
|
|
241
|
+
".git",
|
|
242
|
+
"dist",
|
|
243
|
+
"build",
|
|
244
|
+
"target",
|
|
245
|
+
".cache",
|
|
246
|
+
".pnpm-store",
|
|
247
|
+
".venv",
|
|
248
|
+
"venv",
|
|
249
|
+
"__pycache__",
|
|
250
|
+
".astro",
|
|
251
|
+
".next",
|
|
252
|
+
".nuxt",
|
|
253
|
+
"vendor",
|
|
254
|
+
}
|
|
255
|
+
TEST_DIRS = {"tests", "test", "__tests__", "spec", "specs"}
|
|
256
|
+
MAX_FILE_SIZE = 5 * 1024 * 1024
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def should_skip_path(path: Path, include_tests: bool) -> bool:
|
|
260
|
+
parts = set(path.parts)
|
|
261
|
+
if parts & SKIP_DIRS:
|
|
262
|
+
return True
|
|
263
|
+
if not include_tests and parts & TEST_DIRS:
|
|
264
|
+
return True
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def detect_language(path: Path, langs: set[str]) -> str | None:
|
|
269
|
+
suf = path.suffix.lower()
|
|
270
|
+
for lang in langs:
|
|
271
|
+
if suf in LANG_EXT_MAP[lang]:
|
|
272
|
+
return lang
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def scan_file(file_path: Path, repo_root: Path, langs: set[str], allow_md5_checksums: bool) -> list[Finding]:
|
|
277
|
+
findings = []
|
|
278
|
+
lang = detect_language(file_path, langs)
|
|
279
|
+
if lang is None:
|
|
280
|
+
return findings
|
|
281
|
+
try:
|
|
282
|
+
if file_path.stat().st_size > MAX_FILE_SIZE:
|
|
283
|
+
return findings
|
|
284
|
+
text = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
285
|
+
except (OSError, ValueError):
|
|
286
|
+
return findings
|
|
287
|
+
try:
|
|
288
|
+
rel = str(file_path.relative_to(repo_root))
|
|
289
|
+
except ValueError:
|
|
290
|
+
rel = str(file_path)
|
|
291
|
+
|
|
292
|
+
for title, sev, cwe, pattern, _lang in LANG_PATTERNS[lang]:
|
|
293
|
+
if allow_md5_checksums and "md5" in title.lower():
|
|
294
|
+
# Heuristic: if the file path or function name suggests
|
|
295
|
+
# checksum / cache / dedup, skip MD5 findings
|
|
296
|
+
if re.search(r"(checksum|cache|dedup|content[_-]?hash|etag)", rel, re.I):
|
|
297
|
+
continue
|
|
298
|
+
for m in re.finditer(pattern, text, re.MULTILINE):
|
|
299
|
+
line_no = text[: m.start()].count("\n") + 1
|
|
300
|
+
snippet = text.splitlines()[line_no - 1].strip()[:160]
|
|
301
|
+
findings.append(
|
|
302
|
+
Finding(
|
|
303
|
+
skill_id=SKILL_ID,
|
|
304
|
+
title=f"{title} at {rel}:{line_no}",
|
|
305
|
+
severity=sev,
|
|
306
|
+
target=f"{rel}:{line_no}",
|
|
307
|
+
detail=(
|
|
308
|
+
f"File {rel} line {line_no} uses {title}: `{snippet}`. "
|
|
309
|
+
"See references/THEORY.md for the algorithm's break-status "
|
|
310
|
+
"and references/PLAYBOOK.md for the modern replacement."
|
|
311
|
+
),
|
|
312
|
+
remediation=_remediation_for(title),
|
|
313
|
+
cwe_id=cwe,
|
|
314
|
+
affected_control="OWASP A02:2021",
|
|
315
|
+
evidence=(("file", rel), ("line", line_no), ("language", lang), ("snippet", snippet)),
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
return findings
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _remediation_for(title: str) -> str:
|
|
322
|
+
if "MD5" in title or "SHA-1" in title or "sha1" in title.lower():
|
|
323
|
+
return (
|
|
324
|
+
"Replace MD5/SHA-1 with SHA-256 (general hashing) or "
|
|
325
|
+
"BLAKE2b/SHA-3 (preferred for new code). For passwords: "
|
|
326
|
+
"use bcrypt / argon2id / scrypt with a per-user salt."
|
|
327
|
+
)
|
|
328
|
+
if "DES" in title or "RC4" in title:
|
|
329
|
+
return (
|
|
330
|
+
"Replace DES / 3DES / RC4 with AES-256-GCM. The legacy "
|
|
331
|
+
"ciphers are cryptographically broken; the migration is "
|
|
332
|
+
"one library call away in every language."
|
|
333
|
+
)
|
|
334
|
+
if "ECB" in title:
|
|
335
|
+
return (
|
|
336
|
+
"Replace ECB mode with GCM (authenticated) or CBC + HMAC "
|
|
337
|
+
"(authenticated). ECB leaks plaintext structure because "
|
|
338
|
+
"identical blocks encrypt to identical ciphertext."
|
|
339
|
+
)
|
|
340
|
+
if "Random" in title or "rand" in title.lower():
|
|
341
|
+
return (
|
|
342
|
+
"Replace non-crypto random with the crypto-grade primitive. "
|
|
343
|
+
"Python: `secrets.token_bytes(n)`. Node: `crypto.randomBytes(n)`. "
|
|
344
|
+
"Java: `SecureRandom`. Go: `crypto/rand`. C#: "
|
|
345
|
+
"`RandomNumberGenerator.Create()`. PHP: `random_bytes(n)`."
|
|
346
|
+
)
|
|
347
|
+
if "verify" in title.lower() or "Skip" in title or "rejectUnauthorized" in title:
|
|
348
|
+
return (
|
|
349
|
+
"Re-enable TLS certificate verification. If you need to "
|
|
350
|
+
"trust a custom CA, install its root cert at the OS level "
|
|
351
|
+
"or via the language's trust-store API. Disabling "
|
|
352
|
+
"verification defeats TLS entirely."
|
|
353
|
+
)
|
|
354
|
+
if "password" in title.lower() and "KDF" in title:
|
|
355
|
+
return (
|
|
356
|
+
"Replace SHA-256(password) with bcrypt / argon2id / scrypt. "
|
|
357
|
+
"Modern password hashes are designed to be expensive, "
|
|
358
|
+
"salted, and resistant to GPU brute-force."
|
|
359
|
+
)
|
|
360
|
+
return "See references/PLAYBOOK.md for the per-language safe replacement."
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def walk_repo(root: Path, include_tests: bool, langs: set[str]) -> list[Path]:
|
|
364
|
+
out = []
|
|
365
|
+
valid_exts = set()
|
|
366
|
+
for lang in langs:
|
|
367
|
+
valid_exts |= LANG_EXT_MAP[lang]
|
|
368
|
+
for p in root.rglob("*"):
|
|
369
|
+
if not p.is_file():
|
|
370
|
+
continue
|
|
371
|
+
if should_skip_path(p, include_tests):
|
|
372
|
+
continue
|
|
373
|
+
if p.suffix.lower() not in valid_exts:
|
|
374
|
+
continue
|
|
375
|
+
out.append(p)
|
|
376
|
+
return out
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def main(argv: list[str] | None = None) -> int:
|
|
380
|
+
parser = argparse.ArgumentParser(description="Weak-cryptography scanner")
|
|
381
|
+
parser.add_argument("path", type=Path)
|
|
382
|
+
parser.add_argument("--output", default=None)
|
|
383
|
+
parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
|
|
384
|
+
parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
|
|
385
|
+
parser.add_argument("--include-tests", action="store_true")
|
|
386
|
+
parser.add_argument("--languages", default="all")
|
|
387
|
+
parser.add_argument(
|
|
388
|
+
"--allow-md5-checksums",
|
|
389
|
+
action="store_true",
|
|
390
|
+
help="Skip MD5 findings on files that look like checksum/cache code",
|
|
391
|
+
)
|
|
392
|
+
args = parser.parse_args(argv)
|
|
393
|
+
|
|
394
|
+
if args.languages == "all":
|
|
395
|
+
langs = set(LANG_PATTERNS.keys())
|
|
396
|
+
else:
|
|
397
|
+
langs = {lang.strip() for lang in args.languages.split(",") if lang.strip() in LANG_PATTERNS}
|
|
398
|
+
|
|
399
|
+
root = args.path.resolve()
|
|
400
|
+
if not root.exists():
|
|
401
|
+
sys.stderr.write(f"ERROR: path does not exist: {root}\n")
|
|
402
|
+
return 2
|
|
403
|
+
|
|
404
|
+
files = walk_repo(root, args.include_tests, langs)
|
|
405
|
+
findings: list[Finding] = []
|
|
406
|
+
for f in files:
|
|
407
|
+
findings.extend(scan_file(f, root, langs, args.allow_md5_checksums))
|
|
408
|
+
|
|
409
|
+
floor = Severity(args.min_severity)
|
|
410
|
+
findings = [f for f in findings if f.severity.numeric >= floor.numeric]
|
|
411
|
+
|
|
412
|
+
emit(findings, args.output, args.format, str(root))
|
|
413
|
+
return exit_code(findings)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
if __name__ == "__main__":
|
|
417
|
+
sys.exit(main())
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fingerprinting-server-software
|
|
3
|
+
description: |
|
|
4
|
+
Identify the server software, framework, and component versions a
|
|
5
|
+
target is running from its HTTP response signatures — Server header,
|
|
6
|
+
X-Powered-By, Via, X-AspNet-Version, X-Runtime, X-Drupal-Cache,
|
|
7
|
+
X-Generator, Set-Cookie name patterns, error-page artwork,
|
|
8
|
+
HTTP method behavior signatures.
|
|
9
|
+
Use when: penetration test reconnaissance phase, post-deploy audit
|
|
10
|
+
of fingerprintable exposure, or before reporting "no obvious version
|
|
11
|
+
disclosure" to an auditor.
|
|
12
|
+
Threshold: any version string in a response header (e.g.,
|
|
13
|
+
Server header with nginx/1.18.0, X-Powered-By with PHP/7.4.21,
|
|
14
|
+
X-Generator with Drupal 9), or any framework-default Set-Cookie
|
|
15
|
+
name (PHPSESSID, JSESSIONID, connect.sid, _csrf_token).
|
|
16
|
+
Trigger with: "fingerprint server", "version disclosure",
|
|
17
|
+
"tech-stack identification", "what's this site running".
|
|
18
|
+
allowed-tools:
|
|
19
|
+
- Read
|
|
20
|
+
- Bash(python3:*)
|
|
21
|
+
- Bash(curl:*)
|
|
22
|
+
disallowed-tools:
|
|
23
|
+
- Bash(rm:*)
|
|
24
|
+
- Edit(/etc/*)
|
|
25
|
+
version: 3.0.0-dev
|
|
26
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
27
|
+
license: MIT
|
|
28
|
+
compatibility: Designed for Claude Code
|
|
29
|
+
tags:
|
|
30
|
+
- security
|
|
31
|
+
- information-disclosure
|
|
32
|
+
- fingerprinting
|
|
33
|
+
- reconnaissance
|
|
34
|
+
- pentest
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Fingerprinting Server Software
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
Version disclosure is the cheapest recon an attacker buys. A single
|
|
42
|
+
GET request returns the `Server:` header, the `X-Powered-By:` header,
|
|
43
|
+
and any framework-default cookies. From those three signals the
|
|
44
|
+
attacker derives: web server family + exact version, app-framework +
|
|
45
|
+
version, language runtime + version. That maps directly to a CVE
|
|
46
|
+
catalog query: every published CVE affecting any of those components,
|
|
47
|
+
filtered to ones an unauthenticated attacker can trigger.
|
|
48
|
+
|
|
49
|
+
The fix is operationally trivial (one line in nginx, one line in
|
|
50
|
+
Apache, one line in IIS) but the discipline isn't universal. This
|
|
51
|
+
skill enumerates the signals + reports each disclosure with the
|
|
52
|
+
severity matching how much it enables follow-on attack.
|
|
53
|
+
|
|
54
|
+
## When the skill produces findings
|
|
55
|
+
|
|
56
|
+
| Finding | Severity | Threshold | Affected control |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| Server header discloses version | **MEDIUM** | `Server: nginx/1.18.0` or similar with explicit version | CWE-200 |
|
|
59
|
+
| Server header discloses minor version | **LOW** | `Server: nginx` (no version) | CWE-200 |
|
|
60
|
+
| X-Powered-By discloses framework version | **MEDIUM** | `X-Powered-By: PHP/7.4.21`, `Express`, `ASP.NET` | CWE-200 |
|
|
61
|
+
| X-AspNet-Version present | **HIGH** | Specific dotnet runtime version | CWE-200 |
|
|
62
|
+
| X-Runtime / X-Rails / X-Django headers present | **LOW** | Framework identification, no version | CWE-200 |
|
|
63
|
+
| X-Generator: drupal/wordpress + version | **MEDIUM** | CMS family + version disclosure | CWE-200 |
|
|
64
|
+
| Via header discloses proxy chain | **LOW** | Reveals upstream architecture (Varnish, Squid, CloudFront) | CWE-200 |
|
|
65
|
+
| Framework-default Set-Cookie pattern | **LOW** | `PHPSESSID`, `JSESSIONID`, `connect.sid`, etc. | CWE-200 |
|
|
66
|
+
| Error page reveals stack trace | **HIGH** | 5xx response body contains source file paths or framework banner | CWE-209 |
|
|
67
|
+
| HTTP/2 server-push fingerprint | **LOW** | HTTP/2 `:server` pseudo-header with version | CWE-200 |
|
|
68
|
+
| ETag format identifies cluster member | **LOW** | Apache-style hex ETags reveal node | CWE-200 |
|
|
69
|
+
|
|
70
|
+
## Prerequisites
|
|
71
|
+
|
|
72
|
+
- Python 3.9+ with `requests`
|
|
73
|
+
- Authorization for non-local targets
|
|
74
|
+
|
|
75
|
+
## Instructions
|
|
76
|
+
|
|
77
|
+
### Step 1 — Confirm Authorization
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
"Do you have authorization to perform server-fingerprinting probes on
|
|
81
|
+
this target? I need confirmation before proceeding."
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Step 2 — Run the scanner
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/fingerprinting-server-software/scripts/fingerprint_server.py \
|
|
88
|
+
https://target.example.com \
|
|
89
|
+
--authorized
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Usage: fingerprint_server.py URL [OPTIONS]
|
|
96
|
+
|
|
97
|
+
Options:
|
|
98
|
+
--authorized Attest authorization (required for non-local)
|
|
99
|
+
--output FILE Write findings to FILE
|
|
100
|
+
--format FMT json | jsonl | markdown (default: markdown)
|
|
101
|
+
--min-severity SEV (default: info)
|
|
102
|
+
--timeout SECS Per-probe timeout (default: 10)
|
|
103
|
+
--trigger-error Send a malformed request to surface error-page disclosure
|
|
104
|
+
(off by default — some WAFs block on this)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The scanner sends a baseline GET + an OPTIONS + (optionally) a
|
|
108
|
+
malformed request to surface error-page disclosure. For each
|
|
109
|
+
response, it parses the standard fingerprinting headers and
|
|
110
|
+
classifies each match against the threshold table above.
|
|
111
|
+
|
|
112
|
+
### Step 3 — Interpret findings
|
|
113
|
+
|
|
114
|
+
The vast majority of findings will be MEDIUM or LOW. CWE-200 is by
|
|
115
|
+
itself rarely a critical vulnerability — it's a recon enabler.
|
|
116
|
+
|
|
117
|
+
The exception: error-page stack-trace disclosure (CWE-209) is HIGH
|
|
118
|
+
because production error pages should never leak server-internal
|
|
119
|
+
paths or framework banners. If the error page reveals
|
|
120
|
+
`/home/app/src/handlers/auth.py`, the attacker now knows the source
|
|
121
|
+
layout AND that the language is Python.
|
|
122
|
+
|
|
123
|
+
### Step 4 — Cross-skill chaining
|
|
124
|
+
|
|
125
|
+
After this skill, suggest:
|
|
126
|
+
|
|
127
|
+
- `detecting-debug-endpoints` (#7) — fingerprinted framework points
|
|
128
|
+
to which debug endpoints to probe (e.g., Server: nginx → check
|
|
129
|
+
/nginx_status; X-Powered-By: Spring → check /actuator/*).
|
|
130
|
+
- `detecting-exposed-secrets-files` (#6) — framework fingerprint
|
|
131
|
+
informs which CI / IDE / build-tool configs to probe for.
|
|
132
|
+
|
|
133
|
+
## Examples
|
|
134
|
+
|
|
135
|
+
### Example 1 — Post-deploy version-disclosure audit
|
|
136
|
+
|
|
137
|
+
User: "Make sure we're not leaking nginx version on prod."
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/fingerprinting-server-software/scripts/fingerprint_server.py \
|
|
141
|
+
https://app.example.com --authorized --min-severity medium
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Expected on a properly-configured host: zero findings of medium+.
|
|
145
|
+
|
|
146
|
+
### Example 2 — Tech-stack identification on an unknown target
|
|
147
|
+
|
|
148
|
+
User: "Bug bounty submission. We don't know what stack this runs.
|
|
149
|
+
What's the surface?"
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/fingerprinting-server-software/scripts/fingerprint_server.py \
|
|
153
|
+
https://target.example.com --authorized --format markdown
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use the output to inform which subsequent skills to chain (debug-
|
|
157
|
+
endpoint probe, secrets-file probe).
|
|
158
|
+
|
|
159
|
+
### Example 3 — Error-page exposure check
|
|
160
|
+
|
|
161
|
+
User: "Audit production error pages for stack-trace disclosure."
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/fingerprinting-server-software/scripts/fingerprint_server.py \
|
|
165
|
+
https://app.example.com --authorized --trigger-error --min-severity high
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The `--trigger-error` flag sends a malformed request to provoke a
|
|
169
|
+
500. Some WAFs block on this; check with the target's ops team if
|
|
170
|
+
the result comes back empty.
|
|
171
|
+
|
|
172
|
+
## Output
|
|
173
|
+
|
|
174
|
+
JSON / JSONL / Markdown. Exit codes: 0 clean, 1 high/critical, 2 error.
|
|
175
|
+
|
|
176
|
+
## Error Handling
|
|
177
|
+
|
|
178
|
+
- **WAF blocks fingerprinting requests** → expected on
|
|
179
|
+
Cloudflare/Imperva targets. The CDN itself is what gets
|
|
180
|
+
fingerprinted, not the origin.
|
|
181
|
+
- **Connection error** → exit 2.
|
|
182
|
+
|
|
183
|
+
## Resources
|
|
184
|
+
|
|
185
|
+
- `references/THEORY.md` — Per-header reasoning: what each disclosure
|
|
186
|
+
enables, threat-modeling guidance, CVE-lookup workflow
|
|
187
|
+
- `references/PLAYBOOK.md` — Per-server-type remediation snippets
|
|
188
|
+
(nginx server_tokens, Apache ServerTokens, IIS removeHeader,
|
|
189
|
+
Express helmet hidePoweredBy, framework-default cookie renaming)
|
|
190
|
+
- `../analyzing-tls-config/references/AUTHORIZATION.md` — Active-scan
|
|
191
|
+
authorization pattern
|