@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,354 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Static-analysis scan for SQL-injection vulnerable patterns.
|
|
3
|
+
|
|
4
|
+
References:
|
|
5
|
+
CWE-89 Improper Neutralization of Special Elements used in an SQL Command
|
|
6
|
+
OWASP A03:2021 Injection
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
_PLUGIN_ROOT = Path(__file__).resolve().parents[3]
|
|
17
|
+
if str(_PLUGIN_ROOT) not in sys.path:
|
|
18
|
+
sys.path.insert(0, str(_PLUGIN_ROOT))
|
|
19
|
+
|
|
20
|
+
from lib.finding import Finding, Severity # noqa: E402
|
|
21
|
+
from lib.report import emit, exit_code # noqa: E402
|
|
22
|
+
|
|
23
|
+
SKILL_ID = "detecting-sql-injection-patterns"
|
|
24
|
+
|
|
25
|
+
# Build the SQL-keyword detector once
|
|
26
|
+
SQL_KEYWORDS = r"(?:SELECT|INSERT\s+INTO|UPDATE\s+\w|DELETE\s+FROM|REPLACE\s+INTO|MERGE\s+INTO|UPSERT|DROP\s+\w|TRUNCATE|ALTER\s+\w|CREATE\s+\w|FROM\s+\w|WHERE|JOIN|GROUP\s+BY|ORDER\s+BY)"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def py_patterns():
|
|
30
|
+
"""Python SQL-injection patterns."""
|
|
31
|
+
return [
|
|
32
|
+
(
|
|
33
|
+
"Python f-string with SQL keywords",
|
|
34
|
+
Severity.CRITICAL,
|
|
35
|
+
rf"""f["']\s*{SQL_KEYWORDS}\s+[^"']*\{{[^}}]+\}}""",
|
|
36
|
+
"python",
|
|
37
|
+
),
|
|
38
|
+
(
|
|
39
|
+
"Python cursor.execute with f-string argument",
|
|
40
|
+
Severity.CRITICAL,
|
|
41
|
+
r"""\b(?:cursor|conn|db)\s*\.\s*execute(?:many)?\s*\(\s*f["']""",
|
|
42
|
+
"python",
|
|
43
|
+
),
|
|
44
|
+
(
|
|
45
|
+
"Python string concat into SQL keyword string",
|
|
46
|
+
Severity.CRITICAL,
|
|
47
|
+
rf"""["']\s*{SQL_KEYWORDS}[^"']*["']\s*\+\s*\w""",
|
|
48
|
+
"python",
|
|
49
|
+
),
|
|
50
|
+
(
|
|
51
|
+
"Python %-format on SQL string",
|
|
52
|
+
Severity.HIGH,
|
|
53
|
+
rf"""["']\s*{SQL_KEYWORDS}[^"']*%\s*[sdif]?["']\s*%\s*""",
|
|
54
|
+
"python",
|
|
55
|
+
),
|
|
56
|
+
(
|
|
57
|
+
"Python .format() on SQL string",
|
|
58
|
+
Severity.HIGH,
|
|
59
|
+
rf"""["']\s*{SQL_KEYWORDS}[^"']*\{{[^}}]*\}}[^"']*["']\s*\.\s*format\b""",
|
|
60
|
+
"python",
|
|
61
|
+
),
|
|
62
|
+
("Django .extra() with raw SQL", Severity.MEDIUM, r"\.\s*extra\s*\(\s*(?:where|select|tables)\s*=", "python"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def js_patterns():
|
|
67
|
+
"""JavaScript / TypeScript SQL-injection patterns."""
|
|
68
|
+
return [
|
|
69
|
+
(
|
|
70
|
+
"JS template literal with SQL keywords + interpolation",
|
|
71
|
+
Severity.CRITICAL,
|
|
72
|
+
rf"""`\s*{SQL_KEYWORDS}[^`]*\$\{{[^}}]+\}}""",
|
|
73
|
+
"javascript",
|
|
74
|
+
),
|
|
75
|
+
(
|
|
76
|
+
"JS sequelize.query with template literal interpolation",
|
|
77
|
+
Severity.HIGH,
|
|
78
|
+
r"""sequelize\s*\.\s*query\s*\(\s*`[^`]*\$\{""",
|
|
79
|
+
"javascript",
|
|
80
|
+
),
|
|
81
|
+
(
|
|
82
|
+
"JS knex.raw with template literal interpolation",
|
|
83
|
+
Severity.HIGH,
|
|
84
|
+
r"""(?:knex|db)\s*\.\s*raw\s*\(\s*`[^`]*\$\{""",
|
|
85
|
+
"javascript",
|
|
86
|
+
),
|
|
87
|
+
(
|
|
88
|
+
"JS knex.raw with string concat",
|
|
89
|
+
Severity.HIGH,
|
|
90
|
+
r"""(?:knex|db)\s*\.\s*raw\s*\(\s*['"][^'"]*['"]\s*\+\s*\w""",
|
|
91
|
+
"javascript",
|
|
92
|
+
),
|
|
93
|
+
(
|
|
94
|
+
"JS mysql/pg .query with concat",
|
|
95
|
+
Severity.HIGH,
|
|
96
|
+
rf"""\.\s*query\s*\(\s*["']\s*{SQL_KEYWORDS}[^"']*["']\s*\+\s*\w""",
|
|
97
|
+
"javascript",
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def ruby_patterns():
|
|
103
|
+
"""Ruby SQL-injection patterns."""
|
|
104
|
+
return [
|
|
105
|
+
("Rails .where with string interpolation", Severity.HIGH, r"""\.\s*where\s*\(\s*["'][^"']*\#\{""", "ruby"),
|
|
106
|
+
(
|
|
107
|
+
"Rails find_by_sql with string interpolation",
|
|
108
|
+
Severity.HIGH,
|
|
109
|
+
r"""\.\s*find_by_sql\s*\(\s*["'][^"']*\#\{""",
|
|
110
|
+
"ruby",
|
|
111
|
+
),
|
|
112
|
+
(
|
|
113
|
+
"Rails connection.execute with interpolation",
|
|
114
|
+
Severity.CRITICAL,
|
|
115
|
+
r"""ActiveRecord::Base\.connection\s*\.\s*execute\s*\(\s*["'][^"']*\#\{""",
|
|
116
|
+
"ruby",
|
|
117
|
+
),
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def go_patterns():
|
|
122
|
+
"""Go SQL-injection patterns."""
|
|
123
|
+
return [
|
|
124
|
+
(
|
|
125
|
+
"Go fmt.Sprintf into db.Query/QueryRow/Exec",
|
|
126
|
+
Severity.HIGH,
|
|
127
|
+
r"""(?:Query|QueryRow|Exec)(?:Context)?\s*\(\s*fmt\.Sprintf\s*\(""",
|
|
128
|
+
"go",
|
|
129
|
+
),
|
|
130
|
+
(
|
|
131
|
+
"Go string concat into db.Query",
|
|
132
|
+
Severity.CRITICAL,
|
|
133
|
+
rf"""(?:Query|QueryRow|Exec)(?:Context)?\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\+""",
|
|
134
|
+
"go",
|
|
135
|
+
),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def java_patterns():
|
|
140
|
+
"""Java SQL-injection patterns."""
|
|
141
|
+
return [
|
|
142
|
+
(
|
|
143
|
+
"Java Statement.execute with concat",
|
|
144
|
+
Severity.HIGH,
|
|
145
|
+
rf"""(?:Statement|stmt)\.\s*execute(?:Query|Update)?\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\+""",
|
|
146
|
+
"java",
|
|
147
|
+
),
|
|
148
|
+
(
|
|
149
|
+
"Java String.format on SQL string",
|
|
150
|
+
Severity.HIGH,
|
|
151
|
+
rf"""String\.format\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*%[sd]""",
|
|
152
|
+
"java",
|
|
153
|
+
),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def php_patterns():
|
|
158
|
+
"""PHP SQL-injection patterns."""
|
|
159
|
+
return [
|
|
160
|
+
(
|
|
161
|
+
"PHP mysqli_query with interpolated string",
|
|
162
|
+
Severity.CRITICAL,
|
|
163
|
+
rf"""mysqli_query\s*\(\s*\$[a-z_]+\s*,\s*"[^"]*{SQL_KEYWORDS}[^"]*\$""",
|
|
164
|
+
"php",
|
|
165
|
+
),
|
|
166
|
+
(
|
|
167
|
+
"PHP mysql_query with concat",
|
|
168
|
+
Severity.CRITICAL,
|
|
169
|
+
rf"""mysql_query\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\.\s*\$""",
|
|
170
|
+
"php",
|
|
171
|
+
),
|
|
172
|
+
(
|
|
173
|
+
"PHP PDO->query with concat (should be prepare)",
|
|
174
|
+
Severity.HIGH,
|
|
175
|
+
rf"""->query\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\.\s*\$""",
|
|
176
|
+
"php",
|
|
177
|
+
),
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def csharp_patterns():
|
|
182
|
+
"""C# SQL-injection patterns."""
|
|
183
|
+
return [
|
|
184
|
+
(
|
|
185
|
+
"C# SqlCommand with string interpolation",
|
|
186
|
+
Severity.CRITICAL,
|
|
187
|
+
rf"""new SqlCommand\s*\(\s*\$"[^"]*{SQL_KEYWORDS}[^"]*\{{""",
|
|
188
|
+
"csharp",
|
|
189
|
+
),
|
|
190
|
+
(
|
|
191
|
+
"C# SqlCommand with string concat",
|
|
192
|
+
Severity.CRITICAL,
|
|
193
|
+
rf"""new SqlCommand\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\+""",
|
|
194
|
+
"csharp",
|
|
195
|
+
),
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
LANG_EXT_MAP = {
|
|
200
|
+
"python": {".py"},
|
|
201
|
+
"javascript": {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"},
|
|
202
|
+
"ruby": {".rb"},
|
|
203
|
+
"go": {".go"},
|
|
204
|
+
"java": {".java", ".kt", ".scala"},
|
|
205
|
+
"php": {".php"},
|
|
206
|
+
"csharp": {".cs"},
|
|
207
|
+
}
|
|
208
|
+
LANG_PATTERNS = {
|
|
209
|
+
"python": py_patterns,
|
|
210
|
+
"javascript": js_patterns,
|
|
211
|
+
"ruby": ruby_patterns,
|
|
212
|
+
"go": go_patterns,
|
|
213
|
+
"java": java_patterns,
|
|
214
|
+
"php": php_patterns,
|
|
215
|
+
"csharp": csharp_patterns,
|
|
216
|
+
}
|
|
217
|
+
SKIP_DIRS = {
|
|
218
|
+
"node_modules",
|
|
219
|
+
".git",
|
|
220
|
+
"dist",
|
|
221
|
+
"build",
|
|
222
|
+
"target",
|
|
223
|
+
".cache",
|
|
224
|
+
".pnpm-store",
|
|
225
|
+
".venv",
|
|
226
|
+
"venv",
|
|
227
|
+
"__pycache__",
|
|
228
|
+
".astro",
|
|
229
|
+
".next",
|
|
230
|
+
".nuxt",
|
|
231
|
+
"vendor",
|
|
232
|
+
}
|
|
233
|
+
TEST_DIRS = {"tests", "test", "__tests__", "spec", "specs"}
|
|
234
|
+
MAX_FILE_SIZE = 5 * 1024 * 1024
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def should_skip_path(path: Path, include_tests: bool) -> bool:
|
|
238
|
+
parts = set(path.parts)
|
|
239
|
+
if parts & SKIP_DIRS:
|
|
240
|
+
return True
|
|
241
|
+
if not include_tests and parts & TEST_DIRS:
|
|
242
|
+
return True
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def detect_language(path: Path, langs: set[str]) -> str | None:
|
|
247
|
+
suf = path.suffix.lower()
|
|
248
|
+
for lang in langs:
|
|
249
|
+
if suf in LANG_EXT_MAP[lang]:
|
|
250
|
+
return lang
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def scan_file(file_path: Path, repo_root: Path, langs: set[str]) -> list[Finding]:
|
|
255
|
+
findings = []
|
|
256
|
+
lang = detect_language(file_path, langs)
|
|
257
|
+
if lang is None:
|
|
258
|
+
return findings
|
|
259
|
+
try:
|
|
260
|
+
if file_path.stat().st_size > MAX_FILE_SIZE:
|
|
261
|
+
return findings
|
|
262
|
+
text = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
263
|
+
except (OSError, ValueError):
|
|
264
|
+
return findings
|
|
265
|
+
try:
|
|
266
|
+
rel = str(file_path.relative_to(repo_root))
|
|
267
|
+
except ValueError:
|
|
268
|
+
rel = str(file_path)
|
|
269
|
+
|
|
270
|
+
for title, sev, pattern, _lang in LANG_PATTERNS[lang]():
|
|
271
|
+
for m in re.finditer(pattern, text, re.IGNORECASE | re.MULTILINE):
|
|
272
|
+
line_no = text[: m.start()].count("\n") + 1
|
|
273
|
+
snippet = text.splitlines()[line_no - 1].strip()[:160]
|
|
274
|
+
findings.append(
|
|
275
|
+
Finding(
|
|
276
|
+
skill_id=SKILL_ID,
|
|
277
|
+
title=f"{title} at {rel}:{line_no}",
|
|
278
|
+
severity=sev,
|
|
279
|
+
target=f"{rel}:{line_no}",
|
|
280
|
+
detail=(
|
|
281
|
+
f"File {rel} line {line_no} matches the {title} "
|
|
282
|
+
f"pattern: `{snippet}`. If the interpolated value can "
|
|
283
|
+
"be influenced by external input, this is a "
|
|
284
|
+
"SQL-injection vector."
|
|
285
|
+
),
|
|
286
|
+
remediation=(
|
|
287
|
+
"Replace the string-built query with a parameterized "
|
|
288
|
+
"query API. Per language: Python sqlite3/psycopg uses "
|
|
289
|
+
"`?` or `%s` placeholders; Node mysql2/pg uses `?` or "
|
|
290
|
+
"`$1`; Ruby uses `where(...)` with hash args; Go uses "
|
|
291
|
+
'`db.Query("SELECT ... WHERE x = ?", arg)`; Java '
|
|
292
|
+
"PreparedStatement with `setString(1, val)`. See "
|
|
293
|
+
"references/PLAYBOOK.md for per-language patterns."
|
|
294
|
+
),
|
|
295
|
+
cwe_id="CWE-89",
|
|
296
|
+
affected_control="OWASP A03:2021",
|
|
297
|
+
evidence=(("file", rel), ("line", line_no), ("language", lang), ("snippet", snippet)),
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
return findings
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def walk_repo(root: Path, include_tests: bool, langs: set[str]) -> list[Path]:
|
|
304
|
+
out = []
|
|
305
|
+
valid_exts = set()
|
|
306
|
+
for lang in langs:
|
|
307
|
+
valid_exts |= LANG_EXT_MAP[lang]
|
|
308
|
+
for p in root.rglob("*"):
|
|
309
|
+
if not p.is_file():
|
|
310
|
+
continue
|
|
311
|
+
if should_skip_path(p, include_tests):
|
|
312
|
+
continue
|
|
313
|
+
if p.suffix.lower() not in valid_exts:
|
|
314
|
+
continue
|
|
315
|
+
out.append(p)
|
|
316
|
+
return out
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def main(argv: list[str] | None = None) -> int:
|
|
320
|
+
parser = argparse.ArgumentParser(description="SQL-injection pattern scanner")
|
|
321
|
+
parser.add_argument("path", type=Path)
|
|
322
|
+
parser.add_argument("--output", default=None)
|
|
323
|
+
parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
|
|
324
|
+
parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
|
|
325
|
+
parser.add_argument("--include-tests", action="store_true")
|
|
326
|
+
parser.add_argument(
|
|
327
|
+
"--languages", default="all", help="Comma-separated: python,javascript,ruby,go,java,php,csharp (default: all)"
|
|
328
|
+
)
|
|
329
|
+
args = parser.parse_args(argv)
|
|
330
|
+
|
|
331
|
+
if args.languages == "all":
|
|
332
|
+
langs = set(LANG_PATTERNS.keys())
|
|
333
|
+
else:
|
|
334
|
+
langs = {lang.strip() for lang in args.languages.split(",") if lang.strip() in LANG_PATTERNS}
|
|
335
|
+
|
|
336
|
+
root = args.path.resolve()
|
|
337
|
+
if not root.exists():
|
|
338
|
+
sys.stderr.write(f"ERROR: path does not exist: {root}\n")
|
|
339
|
+
return 2
|
|
340
|
+
|
|
341
|
+
files = walk_repo(root, args.include_tests, langs)
|
|
342
|
+
findings: list[Finding] = []
|
|
343
|
+
for f in files:
|
|
344
|
+
findings.extend(scan_file(f, root, langs))
|
|
345
|
+
|
|
346
|
+
floor = Severity(args.min_severity)
|
|
347
|
+
findings = [f for f in findings if f.severity.numeric >= floor.numeric]
|
|
348
|
+
|
|
349
|
+
emit(findings, args.output, args.format, str(root))
|
|
350
|
+
return exit_code(findings)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
if __name__ == "__main__":
|
|
354
|
+
sys.exit(main())
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: detecting-ssl-cert-issues
|
|
3
|
+
description: |
|
|
4
|
+
Audit a target's TLS certificate beyond protocol/expiry — chain ordering,
|
|
5
|
+
OCSP stapling, revocation status, Certificate Transparency presence,
|
|
6
|
+
key-usage flags, and over-broad wildcards.
|
|
7
|
+
Use when: TLS handshake already passes (skill #1 analyzing-tls-config
|
|
8
|
+
cleared) but you suspect the cert posture is fragile. Auditors flag this
|
|
9
|
+
during SOC2 readiness when a renewal slipped or an intermediate was
|
|
10
|
+
rotated.
|
|
11
|
+
Threshold: missing OCSP stapling on production, fewer than 2 SCTs in
|
|
12
|
+
the cert, intermediate served out of order, key usage missing
|
|
13
|
+
digitalSignature/keyEncipherment, revoked cert presented, or wildcard
|
|
14
|
+
scope of 2-level (e.g., *.com is rejection; *.api.example.com is fine).
|
|
15
|
+
Trigger with: "check cert revocation", "audit ocsp", "ct log check",
|
|
16
|
+
"cert chain audit".
|
|
17
|
+
allowed-tools:
|
|
18
|
+
- Read
|
|
19
|
+
- Bash(python3:*)
|
|
20
|
+
- Bash(openssl:*)
|
|
21
|
+
disallowed-tools:
|
|
22
|
+
- Bash(rm:*)
|
|
23
|
+
- Edit(/etc/*)
|
|
24
|
+
- Write(/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
|
+
- tls
|
|
32
|
+
- ocsp
|
|
33
|
+
- certificate-transparency
|
|
34
|
+
- pentest
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Detecting SSL Certificate Issues
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
This skill is the second-level cert audit, run after `analyzing-tls-config`
|
|
42
|
+
clears the protocol+cipher+expiry+hostname basics. It surfaces issues
|
|
43
|
+
that don't break the handshake today but make the cert fragile or open
|
|
44
|
+
to soft-bypass attacks: missing OCSP stapling forces clients to phone
|
|
45
|
+
home to the CA (privacy + latency hit), missing Certificate Transparency
|
|
46
|
+
SCTs are rejected by Chrome since 2018, an out-of-order chain confuses
|
|
47
|
+
older clients, and over-broad wildcards expand the blast radius of any
|
|
48
|
+
future key compromise.
|
|
49
|
+
|
|
50
|
+
## When the skill produces findings
|
|
51
|
+
|
|
52
|
+
| Finding | Severity | Threshold | Affected control |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| Revoked certificate presented | **CRITICAL** | OCSP responder says "revoked" | RFC 6960 |
|
|
55
|
+
| Missing or invalid OCSP staple | **HIGH** | No status_request response on production | RFC 6066, CA/B BR |
|
|
56
|
+
| Fewer than 2 SCTs embedded | **HIGH** | CT-policy violation (Chrome enforces) | RFC 6962, CA/B Baseline Reqs |
|
|
57
|
+
| Intermediate served out of RFC 5246 order | **MEDIUM** | Server sends root before leaf | RFC 5246 §7.4.2 |
|
|
58
|
+
| AIA extension missing | **MEDIUM** | No CA Issuers / OCSP URL in cert | RFC 5280 §4.2.2.1 |
|
|
59
|
+
| Over-broad wildcard | **HIGH** | Scope of 2-level or wider (e.g., *.com) | CA/B Baseline Reqs §3.2.2 |
|
|
60
|
+
| Wildcard at apex SAN | **LOW** | *.example.com without example.com | RFC 6125 §6.4.3 |
|
|
61
|
+
| Key Usage missing digitalSignature | **MEDIUM** | KU bit absent for TLS server cert | RFC 5280 §4.2.1.3 |
|
|
62
|
+
| Cert chain longer than 4 | **LOW** | Performance + trust expansion | CA/B Baseline Reqs |
|
|
63
|
+
|
|
64
|
+
## Prerequisites
|
|
65
|
+
|
|
66
|
+
- Python 3.9+ with `cryptography` library
|
|
67
|
+
- `openssl` CLI 1.1.1+ (for OCSP query + chain enumeration)
|
|
68
|
+
- Authorization for non-local targets (see `references/AUTHORIZATION.md`
|
|
69
|
+
in skill #1 `analyzing-tls-config` for the canonical pattern)
|
|
70
|
+
|
|
71
|
+
## Instructions
|
|
72
|
+
|
|
73
|
+
### Step 1 — Confirm Authorization
|
|
74
|
+
|
|
75
|
+
Active scan; ask the user verbatim:
|
|
76
|
+
|
|
77
|
+
> "Do you have authorization to perform TLS testing on this target?
|
|
78
|
+
> I need confirmation before proceeding."
|
|
79
|
+
|
|
80
|
+
### Step 2 — Run the scanner
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
|
|
84
|
+
https://target.example.com \
|
|
85
|
+
--authorized
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Usage: check_cert_chain.py URL [OPTIONS]
|
|
92
|
+
|
|
93
|
+
Options:
|
|
94
|
+
--authorized Attest authorization for non-local targets (required)
|
|
95
|
+
--port PORT Target port (default: 443)
|
|
96
|
+
--output FILE Write findings to FILE (default: stdout)
|
|
97
|
+
--format FMT json | jsonl | markdown (default: markdown)
|
|
98
|
+
--min-severity SEV critical|high|medium|low|info (default: info)
|
|
99
|
+
--timeout SECS Per-probe timeout (default: 10)
|
|
100
|
+
--skip-ocsp Skip OCSP responder query (offline mode)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 3 — Interpret findings
|
|
104
|
+
|
|
105
|
+
CRITICAL/HIGH map to immediate action items; MEDIUM/LOW to backlog
|
|
106
|
+
hardening. Cross-reference `references/PLAYBOOK.md` for OCSP stapling
|
|
107
|
+
config snippets per server type.
|
|
108
|
+
|
|
109
|
+
### Step 4 — Cross-skill chaining
|
|
110
|
+
|
|
111
|
+
- After this skill, suggest `checking-http-security-headers` (#4) to
|
|
112
|
+
verify HSTS preload status — HSTS preload depends on a clean cert
|
|
113
|
+
chain to be effective.
|
|
114
|
+
- For CI integration patterns, see `references/PLAYBOOK.md` § CI
|
|
115
|
+
posture-monitoring.
|
|
116
|
+
|
|
117
|
+
## Examples
|
|
118
|
+
|
|
119
|
+
### Example 1 — OCSP stapling audit before adopting must-staple
|
|
120
|
+
|
|
121
|
+
User: "We're considering Must-Staple — what's our OCSP stapling posture
|
|
122
|
+
look like across endpoints?"
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
for ENDPOINT in https://api.example.com https://app.example.com https://admin.example.com; do
|
|
126
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
|
|
127
|
+
"$ENDPOINT" --authorized --min-severity medium
|
|
128
|
+
done
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If any endpoint reports "Missing OCSP staple" HIGH, adopting Must-Staple
|
|
132
|
+
on that cert breaks it on next renewal until OCSP-stapling config
|
|
133
|
+
lands. Pair with `references/PLAYBOOK.md` § OCSP stapling for nginx /
|
|
134
|
+
Caddy / Apache config.
|
|
135
|
+
|
|
136
|
+
### Example 2 — CT-log compliance check before public launch
|
|
137
|
+
|
|
138
|
+
User: "Pre-launch — does our cert have enough SCTs for Chrome to trust it?"
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
|
|
142
|
+
https://new-site.example.com --authorized
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The scanner extracts embedded SCTs from the cert's CT extension. <2
|
|
146
|
+
SCTs → HIGH finding; Chrome's CT enforcement policy rejects the
|
|
147
|
+
connection silently in HTTPS, leaving users with a generic error.
|
|
148
|
+
|
|
149
|
+
### Example 3 — Wildcard scope audit
|
|
150
|
+
|
|
151
|
+
User: "An auditor flagged our wildcard cert as too broad."
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
|
|
155
|
+
https://example.com --authorized --format json | jq '.[] | select(.title | contains("wildcard"))'
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The JSON output captures the wildcard scope; pair with the auditor's
|
|
159
|
+
request to either narrow the SAN list or move to per-service certs.
|
|
160
|
+
|
|
161
|
+
## Output
|
|
162
|
+
|
|
163
|
+
JSON / JSONL / Markdown per `lib/report.py`. Exit codes: 0 clean, 1
|
|
164
|
+
high/critical, 2 error.
|
|
165
|
+
|
|
166
|
+
## Error Handling
|
|
167
|
+
|
|
168
|
+
- **OCSP responder timeout** → emitted as MEDIUM finding (not an error
|
|
169
|
+
exit) with note to investigate responder availability.
|
|
170
|
+
- **CT log lookup unavailable** → falls back to embedded-SCT parsing
|
|
171
|
+
only; emits INFO note.
|
|
172
|
+
- **Untrusted cert** → out of scope (skill #1 handles); this skill assumes
|
|
173
|
+
the chain validates and looks at deeper posture.
|
|
174
|
+
|
|
175
|
+
## Resources
|
|
176
|
+
|
|
177
|
+
- `references/THEORY.md` — OCSP, CT, AIA, chain ordering, wildcard
|
|
178
|
+
scope reasoning with RFC anchors
|
|
179
|
+
- `references/PLAYBOOK.md` — OCSP stapling config per server type +
|
|
180
|
+
CT-log compliance + AIA extension correctness
|
|
181
|
+
- `../analyzing-tls-config/references/AUTHORIZATION.md` — canonical ROE
|
|
182
|
+
template + 2-step gate (shared across all active-scan skills)
|