@havoc-security/scanner 0.1.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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +22 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.d.ts +7 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.js +100 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.js.map +1 -0
- package/dist/analyzers/CredentialExposureAnalyzer.d.ts +11 -0
- package/dist/analyzers/CredentialExposureAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/CredentialExposureAnalyzer.js +262 -0
- package/dist/analyzers/CredentialExposureAnalyzer.js.map +1 -0
- package/dist/analyzers/DependencyAuditAnalyzer.d.ts +28 -0
- package/dist/analyzers/DependencyAuditAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/DependencyAuditAnalyzer.js +107 -0
- package/dist/analyzers/DependencyAuditAnalyzer.js.map +1 -0
- package/dist/analyzers/EncryptionAnalyzer.d.ts +7 -0
- package/dist/analyzers/EncryptionAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/EncryptionAnalyzer.js +170 -0
- package/dist/analyzers/EncryptionAnalyzer.js.map +1 -0
- package/dist/analyzers/FileUploadAnalyzer.d.ts +8 -0
- package/dist/analyzers/FileUploadAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/FileUploadAnalyzer.js +193 -0
- package/dist/analyzers/FileUploadAnalyzer.js.map +1 -0
- package/dist/analyzers/IdorAnalyzer.d.ts +7 -0
- package/dist/analyzers/IdorAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/IdorAnalyzer.js +91 -0
- package/dist/analyzers/IdorAnalyzer.js.map +1 -0
- package/dist/analyzers/MassAssignmentAnalyzer.d.ts +7 -0
- package/dist/analyzers/MassAssignmentAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/MassAssignmentAnalyzer.js +90 -0
- package/dist/analyzers/MassAssignmentAnalyzer.js.map +1 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.d.ts +7 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.js +217 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.js.map +1 -0
- package/dist/analyzers/RateLimitAnalyzer.d.ts +7 -0
- package/dist/analyzers/RateLimitAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/RateLimitAnalyzer.js +151 -0
- package/dist/analyzers/RateLimitAnalyzer.js.map +1 -0
- package/dist/analyzers/SessionSecurityAnalyzer.d.ts +10 -0
- package/dist/analyzers/SessionSecurityAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/SessionSecurityAnalyzer.js +295 -0
- package/dist/analyzers/SessionSecurityAnalyzer.js.map +1 -0
- package/dist/analyzers/SqlInjectionAnalyzer.d.ts +7 -0
- package/dist/analyzers/SqlInjectionAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/SqlInjectionAnalyzer.js +77 -0
- package/dist/analyzers/SqlInjectionAnalyzer.js.map +1 -0
- package/dist/analyzers/XssSurfaceAnalyzer.d.ts +7 -0
- package/dist/analyzers/XssSurfaceAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/XssSurfaceAnalyzer.js +100 -0
- package/dist/analyzers/XssSurfaceAnalyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +13 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +13 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/PhpParser.d.ts +56 -0
- package/dist/parsers/PhpParser.d.ts.map +1 -0
- package/dist/parsers/PhpParser.js +193 -0
- package/dist/parsers/PhpParser.js.map +1 -0
- package/dist/parsers/RouteParser.d.ts +87 -0
- package/dist/parsers/RouteParser.d.ts.map +1 -0
- package/dist/parsers/RouteParser.js +327 -0
- package/dist/parsers/RouteParser.js.map +1 -0
- package/dist/rules/index.d.ts +14 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +9 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +30 -0
- package/package.json.bak +27 -0
- package/src/analyzers/AuthorizationCoverageAnalyzer.ts +213 -0
- package/src/analyzers/CredentialExposureAnalyzer.ts +312 -0
- package/src/analyzers/DependencyAuditAnalyzer.ts +135 -0
- package/src/analyzers/EncryptionAnalyzer.ts +195 -0
- package/src/analyzers/FileUploadAnalyzer.ts +239 -0
- package/src/analyzers/IdorAnalyzer.ts +118 -0
- package/src/analyzers/InsecureDeserializationAnalyzer.ts +212 -0
- package/src/analyzers/MassAssignmentAnalyzer.ts +105 -0
- package/src/analyzers/OpenRedirectAnalyzer.ts +149 -0
- package/src/analyzers/PrivilegeEscalationAnalyzer.ts +258 -0
- package/src/analyzers/RateLimitAnalyzer.ts +195 -0
- package/src/analyzers/SecurityHeaderAnalyzer.ts +263 -0
- package/src/analyzers/SessionSecurityAnalyzer.ts +342 -0
- package/src/analyzers/SqlInjectionAnalyzer.ts +99 -0
- package/src/analyzers/XssSurfaceAnalyzer.ts +112 -0
- package/src/analyzers/exclusions.ts +87 -0
- package/src/analyzers/index.ts +15 -0
- package/src/index.ts +226 -0
- package/src/parsers/PhpParser.ts +259 -0
- package/src/parsers/RouteParser.ts +384 -0
- package/src/rules/index.ts +16 -0
- package/src/types/index.ts +164 -0
- package/tests/EncryptionAnalyzer.test.ts +137 -0
- package/tests/PrivilegeEscalationAnalyzer.test.ts +141 -0
- package/tests/RateLimitAnalyzer.test.ts +112 -0
- package/tests/analyzers.test.ts +678 -0
- package/tests/auth-coverage-route-aware.test.ts +294 -0
- package/tests/credential-exposure.test.ts +142 -0
- package/tests/file-upload.test.ts +141 -0
- package/tests/fixtures/app/Http/Controllers/AdminController.php +19 -0
- package/tests/fixtures/app/Http/Controllers/PostController.php +49 -0
- package/tests/fixtures/app/Http/Controllers/PublicController.php +17 -0
- package/tests/fixtures/app/Models/Comment.php +11 -0
- package/tests/fixtures/app/Models/OpenModel.php +11 -0
- package/tests/fixtures/app/Models/Post.php +14 -0
- package/tests/fixtures/app/Models/SafeModel.php +10 -0
- package/tests/fixtures/app/Models/User.php +15 -0
- package/tests/fixtures/blade/mail.blade.php +8 -0
- package/tests/fixtures/blade/safe.blade.php +12 -0
- package/tests/fixtures/blade/vulnerable.blade.php +12 -0
- package/tests/fixtures/controllers/AdminController.php +19 -0
- package/tests/fixtures/controllers/PostController.php +49 -0
- package/tests/fixtures/controllers/PublicController.php +17 -0
- package/tests/fixtures/deserialization/safe.php +32 -0
- package/tests/fixtures/deserialization/unsafe.php +60 -0
- package/tests/fixtures/models/Comment.php +11 -0
- package/tests/fixtures/models/OpenModel.php +11 -0
- package/tests/fixtures/models/Post.php +14 -0
- package/tests/fixtures/models/SafeModel.php +10 -0
- package/tests/fixtures/models/User.php +15 -0
- package/tests/fixtures/redirect/safe.php +38 -0
- package/tests/fixtures/redirect/unsafe.php +39 -0
- package/tests/fixtures/routes/api.php +9 -0
- package/tests/fixtures/routes/web.php +18 -0
- package/tests/fixtures/security-headers/app/Http/Middleware/SecurityHeaders.php +24 -0
- package/tests/fixtures/security-headers/app/Providers/AppServiceProvider.php +16 -0
- package/tests/fixtures/sql/safe_queries.php +7 -0
- package/tests/fixtures/sql/vulnerable_queries.php +7 -0
- package/tests/new-analyzers.test.ts +373 -0
- package/tests/route-parser.test.ts +257 -0
- package/tests/scanner.test.ts +82 -0
- package/tests/session-security.test.ts +161 -0
- package/tests/types.test.ts +29 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Severity } from '../types/index.js';
|
|
2
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
3
|
+
const FINDING_MASS_ASSIGNABLE_PRIVILEGE = 'HAVOC-PE-001';
|
|
4
|
+
const FINDING_UNPROTECTED_ROLE_CHANGE = 'HAVOC-PE-002';
|
|
5
|
+
const FINDING_MISSING_ADMIN_MIDDLEWARE = 'HAVOC-PE-003';
|
|
6
|
+
const ANALYZER_NAME = 'PrivilegeEscalationAnalyzer';
|
|
7
|
+
/** Fields in $fillable that indicate privilege escalation risk. */
|
|
8
|
+
const PRIVILEGE_FILLABLE_FIELDS = new Set([
|
|
9
|
+
'is_admin',
|
|
10
|
+
'is_superadmin',
|
|
11
|
+
'is_super_admin',
|
|
12
|
+
'is_superuser',
|
|
13
|
+
'role',
|
|
14
|
+
'roles',
|
|
15
|
+
'admin',
|
|
16
|
+
'superadmin',
|
|
17
|
+
'permissions',
|
|
18
|
+
'permission',
|
|
19
|
+
'user_type',
|
|
20
|
+
'account_type',
|
|
21
|
+
'access_level',
|
|
22
|
+
]);
|
|
23
|
+
/** Methods that mutate roles/permissions. */
|
|
24
|
+
const ROLE_MUTATION_METHODS = [
|
|
25
|
+
'assignRole',
|
|
26
|
+
'givePermissionTo',
|
|
27
|
+
'syncRoles',
|
|
28
|
+
'syncPermissions',
|
|
29
|
+
'revokePermissionTo',
|
|
30
|
+
'removeRole',
|
|
31
|
+
'syncPermissionsWithoutDetaching',
|
|
32
|
+
];
|
|
33
|
+
/** Patterns indicating authorization was checked. */
|
|
34
|
+
const AUTHORIZATION_PATTERNS = [
|
|
35
|
+
/\$this->authorize\s*\(/,
|
|
36
|
+
/Gate::authorize\s*\(/,
|
|
37
|
+
/Gate::allows\s*\(/,
|
|
38
|
+
/Gate::denies\s*\(/,
|
|
39
|
+
/Gate::check\s*\(/,
|
|
40
|
+
/->can\s*\(/,
|
|
41
|
+
/->cannot\s*\(/,
|
|
42
|
+
/abort_if\s*\(/,
|
|
43
|
+
/abort_unless\s*\(/,
|
|
44
|
+
];
|
|
45
|
+
/** Admin-level middleware that protects privilege routes. */
|
|
46
|
+
const ADMIN_MIDDLEWARE_PATTERNS = [
|
|
47
|
+
/['"]role:admin['"]/,
|
|
48
|
+
/['"]can:manage['"]/,
|
|
49
|
+
/['"]can:admin['"]/,
|
|
50
|
+
/['"]admin['"]/,
|
|
51
|
+
/['"]is[._]admin['"]/i,
|
|
52
|
+
/['"]permission:manage['"]/,
|
|
53
|
+
/middleware\s*\(\s*['"][^'"]*admin[^'"]*['"]/,
|
|
54
|
+
];
|
|
55
|
+
function isModelFile(path) {
|
|
56
|
+
return path.includes('app/Models') || path.includes('app\\Models');
|
|
57
|
+
}
|
|
58
|
+
function isControllerFile(path) {
|
|
59
|
+
return path.includes('Http/Controllers') || path.includes('Http\\Controllers');
|
|
60
|
+
}
|
|
61
|
+
function isRouteFile(path) {
|
|
62
|
+
return path.startsWith('routes/') || path.startsWith('routes\\');
|
|
63
|
+
}
|
|
64
|
+
function hasAuthorization(bodyText) {
|
|
65
|
+
return AUTHORIZATION_PATTERNS.some((re) => re.test(bodyText));
|
|
66
|
+
}
|
|
67
|
+
function extractFillableFields(initText) {
|
|
68
|
+
const matches = initText.match(/['"]([^'"]+)['"]/g) ?? [];
|
|
69
|
+
return matches.map((m) => m.replace(/['"]/g, ''));
|
|
70
|
+
}
|
|
71
|
+
function hasAdminMiddleware(routeSnippet) {
|
|
72
|
+
return ADMIN_MIDDLEWARE_PATTERNS.some((p) => p.test(routeSnippet));
|
|
73
|
+
}
|
|
74
|
+
function isAdminRoute(uri) {
|
|
75
|
+
return /admin/i.test(uri);
|
|
76
|
+
}
|
|
77
|
+
export class PrivilegeEscalationAnalyzer {
|
|
78
|
+
name = ANALYZER_NAME;
|
|
79
|
+
description = 'Detects privilege escalation vectors: mass-assignable role fields, unprotected role changes, and missing admin middleware';
|
|
80
|
+
async analyze(files, _config) {
|
|
81
|
+
const findings = [];
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
// ── 1. Check models for mass-assignable privilege fields ────────────────
|
|
84
|
+
if (isModelFile(file.path)) {
|
|
85
|
+
const phpFile = file;
|
|
86
|
+
if (!phpFile.classes)
|
|
87
|
+
continue;
|
|
88
|
+
for (const cls of phpFile.classes) {
|
|
89
|
+
if (cls.isAbstract || cls.isTrait)
|
|
90
|
+
continue;
|
|
91
|
+
const fillableProp = cls.properties.find((p) => p.name === 'fillable');
|
|
92
|
+
if (!fillableProp)
|
|
93
|
+
continue;
|
|
94
|
+
const fields = extractFillableFields(fillableProp.initializer ?? '');
|
|
95
|
+
for (const field of fields) {
|
|
96
|
+
if (PRIVILEGE_FILLABLE_FIELDS.has(field)) {
|
|
97
|
+
findings.push({
|
|
98
|
+
id: FINDING_MASS_ASSIGNABLE_PRIVILEGE,
|
|
99
|
+
severity: Severity.Critical,
|
|
100
|
+
analyzer: ANALYZER_NAME,
|
|
101
|
+
title: `Privilege field \`${field}\` is mass-assignable in \`${cls.name}\``,
|
|
102
|
+
description: `The field \`${field}\` is listed in \`$fillable\` on model \`${cls.name}\`. ` +
|
|
103
|
+
`This means it can be set via mass assignment (e.g., \`User::create($request->all())\`), ` +
|
|
104
|
+
`allowing an attacker to escalate their own privileges by crafting a request ` +
|
|
105
|
+
`with \`${field}=1\` or \`role=admin\`.`,
|
|
106
|
+
file: file.path,
|
|
107
|
+
line: fillableProp.line ?? cls.line,
|
|
108
|
+
recommendation: `Remove \`${field}\` from \`$fillable\` immediately. Use a dedicated method ` +
|
|
109
|
+
`(e.g., \`assignRole()\`, \`promoteToAdmin()\`) with proper authorization checks ` +
|
|
110
|
+
`to modify this field.`,
|
|
111
|
+
cwe: 'CWE-269',
|
|
112
|
+
snippet: `protected $fillable = [..., '${field}', ...];`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ── 2. Check controllers for role mutations without authorization ───────
|
|
119
|
+
if (isControllerFile(file.path)) {
|
|
120
|
+
const phpFile = file;
|
|
121
|
+
if (!phpFile.classes)
|
|
122
|
+
continue;
|
|
123
|
+
for (const cls of phpFile.classes) {
|
|
124
|
+
for (const method of cls.methods) {
|
|
125
|
+
if (method.visibility !== 'public')
|
|
126
|
+
continue;
|
|
127
|
+
const body = method.bodyText;
|
|
128
|
+
const hasRoleMutation = ROLE_MUTATION_METHODS.some((m) => new RegExp(`->${m}\\s*\\(`).test(body));
|
|
129
|
+
if (!hasRoleMutation)
|
|
130
|
+
continue;
|
|
131
|
+
const mutatingMethod = ROLE_MUTATION_METHODS.find((m) => new RegExp(`->${m}\\s*\\(`).test(body)) ?? 'unknown';
|
|
132
|
+
if (!hasAuthorization(body)) {
|
|
133
|
+
findings.push({
|
|
134
|
+
id: FINDING_UNPROTECTED_ROLE_CHANGE,
|
|
135
|
+
severity: Severity.High,
|
|
136
|
+
analyzer: ANALYZER_NAME,
|
|
137
|
+
title: `Role mutation \`${mutatingMethod}()\` without authorization in \`${cls.name}::${method.name}()\``,
|
|
138
|
+
description: `The method \`${method.name}()\` in \`${cls.name}\` calls \`${mutatingMethod}()\` ` +
|
|
139
|
+
`to modify roles or permissions, but no authorization check (\`$this->authorize()\`, ` +
|
|
140
|
+
`\`Gate::authorize()\`, etc.) was detected before the mutation. This may allow ` +
|
|
141
|
+
`unauthorized privilege escalation.`,
|
|
142
|
+
file: file.path,
|
|
143
|
+
line: method.line,
|
|
144
|
+
recommendation: `Add \`$this->authorize('manage-roles')\` or an equivalent Gate/Policy check ` +
|
|
145
|
+
`at the top of \`${method.name}()\` before calling \`${mutatingMethod}()\`. ` +
|
|
146
|
+
`Ensure only admins can modify roles.`,
|
|
147
|
+
cwe: 'CWE-269',
|
|
148
|
+
snippet: body.slice(0, 200),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Check for self-referential role changes: $user = auth()->user() or similar
|
|
152
|
+
const selfUpdatePatterns = [
|
|
153
|
+
/auth\(\)->user\(\)/,
|
|
154
|
+
/\$request->user\(\)/,
|
|
155
|
+
/Auth::user\(\)/,
|
|
156
|
+
];
|
|
157
|
+
const hasSelfUpdate = selfUpdatePatterns.some((p) => p.test(body));
|
|
158
|
+
if (hasSelfUpdate) {
|
|
159
|
+
findings.push({
|
|
160
|
+
id: FINDING_UNPROTECTED_ROLE_CHANGE,
|
|
161
|
+
severity: Severity.High,
|
|
162
|
+
analyzer: ANALYZER_NAME,
|
|
163
|
+
title: `Possible self-referential role change in \`${cls.name}::${method.name}()\``,
|
|
164
|
+
description: `The method \`${method.name}()\` in \`${cls.name}\` appears to call ` +
|
|
165
|
+
`\`${mutatingMethod}()\` on the authenticated user (\`auth()->user()\`), which ` +
|
|
166
|
+
`may allow a user to escalate their own privileges.`,
|
|
167
|
+
file: file.path,
|
|
168
|
+
line: method.line,
|
|
169
|
+
recommendation: `Ensure role/permission changes can only be performed by an admin on OTHER users. ` +
|
|
170
|
+
`Add a check like: \`abort_if(auth()->id() === $targetUser->id, 403)\`.`,
|
|
171
|
+
cwe: 'CWE-269',
|
|
172
|
+
snippet: body.slice(0, 200),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ── 3. Check route files for admin routes without admin middleware ───────
|
|
179
|
+
if (isRouteFile(file.path)) {
|
|
180
|
+
const routeRegex = /Route::(get|post|put|patch|delete|any)\s*\(\s*['"`]([^'"`]+)['"`][^;]*\);/g;
|
|
181
|
+
let match;
|
|
182
|
+
while ((match = routeRegex.exec(file.content)) !== null) {
|
|
183
|
+
const uri = match[2];
|
|
184
|
+
if (!isAdminRoute(uri))
|
|
185
|
+
continue;
|
|
186
|
+
const lineStart = file.content.lastIndexOf('\n', match.index) + 1;
|
|
187
|
+
const snippet = file.content.slice(lineStart, match.index + match[0].length).trim();
|
|
188
|
+
// Check if the route itself has admin middleware, or look for enclosing group
|
|
189
|
+
// by checking nearby context (simple heuristic: 200 chars before)
|
|
190
|
+
const context = file.content.slice(Math.max(0, match.index - 300), match.index + match[0].length);
|
|
191
|
+
if (!hasAdminMiddleware(context)) {
|
|
192
|
+
const beforeMatch = file.content.slice(0, match.index);
|
|
193
|
+
const line = (beforeMatch.match(/\n/g) ?? []).length + 1;
|
|
194
|
+
findings.push({
|
|
195
|
+
id: FINDING_MISSING_ADMIN_MIDDLEWARE,
|
|
196
|
+
severity: Severity.Medium,
|
|
197
|
+
analyzer: ANALYZER_NAME,
|
|
198
|
+
title: `Admin route \`${uri}\` may be missing admin middleware`,
|
|
199
|
+
description: `The route \`${match[1].toUpperCase()} ${uri}\` appears to be an admin endpoint ` +
|
|
200
|
+
`but no \`role:admin\`, \`can:manage-users\`, or similar admin middleware was ` +
|
|
201
|
+
`detected in its definition or enclosing group.`,
|
|
202
|
+
file: file.path,
|
|
203
|
+
line,
|
|
204
|
+
recommendation: `Wrap admin routes in a middleware group: ` +
|
|
205
|
+
`\`Route::middleware(['auth', 'role:admin'])->group(...)\`. ` +
|
|
206
|
+
`Use Spatie's \`role\` middleware or a custom admin middleware.`,
|
|
207
|
+
cwe: 'CWE-269',
|
|
208
|
+
snippet: snippet.slice(0, 200),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return findings;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=PrivilegeEscalationAnalyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrivilegeEscalationAnalyzer.js","sourceRoot":"","sources":["../../src/analyzers/PrivilegeEscalationAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGzF,iFAAiF;AAEjF,MAAM,iCAAiC,GAAG,cAAc,CAAC;AACzD,MAAM,+BAA+B,GAAG,cAAc,CAAC;AACvD,MAAM,gCAAgC,GAAG,cAAc,CAAC;AACxD,MAAM,aAAa,GAAG,6BAA6B,CAAC;AAEpD,mEAAmE;AACnE,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACxC,UAAU;IACV,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,MAAM;IACN,OAAO;IACP,OAAO;IACP,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,WAAW;IACX,cAAc;IACd,cAAc;CACf,CAAC,CAAC;AAEH,6CAA6C;AAC7C,MAAM,qBAAqB,GAAG;IAC5B,YAAY;IACZ,kBAAkB;IAClB,WAAW;IACX,iBAAiB;IACjB,oBAAoB;IACpB,YAAY;IACZ,iCAAiC;CAClC,CAAC;AAEF,qDAAqD;AACrD,MAAM,sBAAsB,GAAG;IAC7B,wBAAwB;IACxB,sBAAsB;IACtB,mBAAmB;IACnB,mBAAmB;IACnB,kBAAkB;IAClB,YAAY;IACZ,eAAe;IACf,eAAe;IACf,mBAAmB;CACpB,CAAC;AAEF,6DAA6D;AAC7D,MAAM,yBAAyB,GAAG;IAChC,oBAAoB;IACpB,oBAAoB;IACpB,mBAAmB;IACnB,eAAe;IACf,sBAAsB;IACtB,2BAA2B;IAC3B,6CAA6C;CAC9C,CAAC;AAEF,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB;IACxC,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC;IAC1D,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAoB;IAC9C,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,OAAO,2BAA2B;IAC7B,IAAI,GAAG,aAAa,CAAC;IACrB,WAAW,GAClB,2HAA2H,CAAC;IAE9H,KAAK,CAAC,OAAO,CAAC,KAAmB,EAAE,OAAoB;QACrD,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,2EAA2E;YAC3E,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,MAAM,OAAO,GAAG,IAAqB,CAAC;gBACtC,IAAI,CAAC,OAAO,CAAC,OAAO;oBAAE,SAAS;gBAE/B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBAClC,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,OAAO;wBAAE,SAAS;oBAE5C,MAAM,YAAY,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;oBACvE,IAAI,CAAC,YAAY;wBAAE,SAAS;oBAE5B,MAAM,MAAM,GAAG,qBAAqB,CAAC,YAAY,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;oBACrE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;wBAC3B,IAAI,yBAAyB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;4BACzC,QAAQ,CAAC,IAAI,CAAC;gCACZ,EAAE,EAAE,iCAAiC;gCACrC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gCAC3B,QAAQ,EAAE,aAAa;gCACvB,KAAK,EAAE,qBAAqB,KAAK,8BAA8B,GAAG,CAAC,IAAI,IAAI;gCAC3E,WAAW,EACT,eAAe,KAAK,4CAA4C,GAAG,CAAC,IAAI,MAAM;oCAC9E,0FAA0F;oCAC1F,8EAA8E;oCAC9E,UAAU,KAAK,yBAAyB;gCAC1C,IAAI,EAAE,IAAI,CAAC,IAAI;gCACf,IAAI,EAAE,YAAY,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI;gCACnC,cAAc,EACZ,YAAY,KAAK,4DAA4D;oCAC7E,kFAAkF;oCAClF,uBAAuB;gCACzB,GAAG,EAAE,SAAS;gCACd,OAAO,EAAE,gCAAgC,KAAK,UAAU;6BACzD,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,2EAA2E;YAC3E,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,MAAM,OAAO,GAAG,IAAqB,CAAC;gBACtC,IAAI,CAAC,OAAO,CAAC,OAAO;oBAAE,SAAS;gBAE/B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBAClC,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;wBACjC,IAAI,MAAM,CAAC,UAAU,KAAK,QAAQ;4BAAE,SAAS;wBAE7C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC;wBAC7B,MAAM,eAAe,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACvD,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACvC,CAAC;wBAEF,IAAI,CAAC,eAAe;4BAAE,SAAS;wBAE/B,MAAM,cAAc,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACtD,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACvC,IAAI,SAAS,CAAC;wBAEf,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;4BAE5B,QAAQ,CAAC,IAAI,CAAC;gCACZ,EAAE,EAAE,+BAA+B;gCACnC,QAAQ,EAAE,QAAQ,CAAC,IAAI;gCACvB,QAAQ,EAAE,aAAa;gCACvB,KAAK,EAAE,mBAAmB,cAAc,mCAAmC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,MAAM;gCACzG,WAAW,EACT,gBAAgB,MAAM,CAAC,IAAI,aAAa,GAAG,CAAC,IAAI,cAAc,cAAc,OAAO;oCACnF,sFAAsF;oCACtF,gFAAgF;oCAChF,oCAAoC;gCACtC,IAAI,EAAE,IAAI,CAAC,IAAI;gCACf,IAAI,EAAE,MAAM,CAAC,IAAI;gCACjB,cAAc,EACZ,8EAA8E;oCAC9E,mBAAmB,MAAM,CAAC,IAAI,yBAAyB,cAAc,QAAQ;oCAC7E,sCAAsC;gCACxC,GAAG,EAAE,SAAS;gCACd,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;6BAC5B,CAAC,CAAC;wBACL,CAAC;wBAED,6EAA6E;wBAC7E,MAAM,kBAAkB,GAAG;4BACzB,oBAAoB;4BACpB,qBAAqB;4BACrB,gBAAgB;yBACjB,CAAC;wBACF,MAAM,aAAa,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;wBACnE,IAAI,aAAa,EAAE,CAAC;4BAClB,QAAQ,CAAC,IAAI,CAAC;gCACZ,EAAE,EAAE,+BAA+B;gCACnC,QAAQ,EAAE,QAAQ,CAAC,IAAI;gCACvB,QAAQ,EAAE,aAAa;gCACvB,KAAK,EAAE,8CAA8C,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,MAAM;gCACnF,WAAW,EACT,gBAAgB,MAAM,CAAC,IAAI,aAAa,GAAG,CAAC,IAAI,qBAAqB;oCACrE,KAAK,cAAc,6DAA6D;oCAChF,oDAAoD;gCACtD,IAAI,EAAE,IAAI,CAAC,IAAI;gCACf,IAAI,EAAE,MAAM,CAAC,IAAI;gCACjB,cAAc,EACZ,mFAAmF;oCACnF,wEAAwE;gCAC1E,GAAG,EAAE,SAAS;gCACd,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;6BAC5B,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,4EAA4E;YAC5E,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,MAAM,UAAU,GACd,4EAA4E,CAAC;gBAC/E,IAAI,KAA6B,CAAC;gBAElC,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;oBACxD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACrB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC;wBAAE,SAAS;oBAEjC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAClE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;oBAEpF,8EAA8E;oBAC9E,kEAAkE;oBAClE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;oBAElG,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;wBACjC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;wBACvD,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;wBAEzD,QAAQ,CAAC,IAAI,CAAC;4BACZ,EAAE,EAAE,gCAAgC;4BACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM;4BACzB,QAAQ,EAAE,aAAa;4BACvB,KAAK,EAAE,iBAAiB,GAAG,oCAAoC;4BAC/D,WAAW,EACT,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,GAAG,qCAAqC;gCACjF,+EAA+E;gCAC/E,gDAAgD;4BAClD,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,IAAI;4BACJ,cAAc,EACZ,2CAA2C;gCAC3C,6DAA6D;gCAC7D,gEAAgE;4BAClE,GAAG,EAAE,SAAS;4BACd,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;yBAC/B,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Analyzer, Finding, HavocConfig, ParsedFile } from '../types/index.js';
|
|
2
|
+
export declare class RateLimitAnalyzer implements Analyzer {
|
|
3
|
+
readonly name = "RateLimitAnalyzer";
|
|
4
|
+
readonly description = "Detects sensitive endpoints (auth, API) missing rate limiting middleware";
|
|
5
|
+
analyze(files: ParsedFile[], _config: HavocConfig): Promise<Finding[]>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=RateLimitAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimitAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzers/RateLimitAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAY,MAAM,mBAAmB,CAAC;AAqIzF,qBAAa,iBAAkB,YAAW,QAAQ;IAChD,QAAQ,CAAC,IAAI,uBAAiB;IAC9B,QAAQ,CAAC,WAAW,8EACyD;IAEvE,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;CAwD7E"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Severity } from '../types/index.js';
|
|
2
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
3
|
+
const FINDING_AUTH_NO_THROTTLE = 'HAVOC-RL-001';
|
|
4
|
+
const FINDING_API_NO_THROTTLE = 'HAVOC-RL-002';
|
|
5
|
+
const ANALYZER_NAME = 'RateLimitAnalyzer';
|
|
6
|
+
/** Patterns that indicate an auth-sensitive endpoint. */
|
|
7
|
+
const AUTH_ENDPOINT_PATTERNS = [
|
|
8
|
+
/login/i,
|
|
9
|
+
/logout/i,
|
|
10
|
+
/register/i,
|
|
11
|
+
/password\/reset/i,
|
|
12
|
+
/password\/email/i,
|
|
13
|
+
/password\/confirm/i,
|
|
14
|
+
/forgot-password/i,
|
|
15
|
+
/reset-password/i,
|
|
16
|
+
/two-factor/i,
|
|
17
|
+
/verify-email/i,
|
|
18
|
+
/webhook/i,
|
|
19
|
+
];
|
|
20
|
+
/** Patterns that identify throttle/rate-limit middleware. */
|
|
21
|
+
const THROTTLE_PATTERNS = [/throttle:/, /RateLimiter/, /rate.limit/i];
|
|
22
|
+
function isRouteFile(path) {
|
|
23
|
+
return path.startsWith('routes/') || path.startsWith('routes\\');
|
|
24
|
+
}
|
|
25
|
+
function hasThrottle(middlewareList) {
|
|
26
|
+
return middlewareList.some((m) => THROTTLE_PATTERNS.some((p) => p.test(m)));
|
|
27
|
+
}
|
|
28
|
+
function isAuthEndpoint(uri) {
|
|
29
|
+
return AUTH_ENDPOINT_PATTERNS.some((p) => p.test(uri));
|
|
30
|
+
}
|
|
31
|
+
function extractMiddlewareList(raw) {
|
|
32
|
+
// Extract quoted strings individually to avoid splitting on commas inside throttle:X,Y
|
|
33
|
+
const matches = raw.match(/['"](throttle:[^'"]+)['"]/g);
|
|
34
|
+
if (matches) {
|
|
35
|
+
// Return the full throttle values plus any other middlewares
|
|
36
|
+
const throttleValues = matches.map((m) => m.replace(/['"]/g, ''));
|
|
37
|
+
const others = raw
|
|
38
|
+
.replace(/[\[\]]/g, '')
|
|
39
|
+
.split(',')
|
|
40
|
+
.map((m) => m.trim().replace(/['"]/g, ''))
|
|
41
|
+
.filter((m) => !m.startsWith('throttle:') && Boolean(m));
|
|
42
|
+
return [...throttleValues, ...others];
|
|
43
|
+
}
|
|
44
|
+
return raw
|
|
45
|
+
.replace(/[\[\]]/g, '')
|
|
46
|
+
.split(',')
|
|
47
|
+
.map((m) => m.trim().replace(/['"]/g, ''))
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
function parseRoutesInBlock(block, inheritedMiddleware) {
|
|
51
|
+
const routes = [];
|
|
52
|
+
const routeRegex = /Route::(get|post|put|patch|delete|any|match|apiResource|resource)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = routeRegex.exec(block)) !== null) {
|
|
55
|
+
const method = match[1].toUpperCase();
|
|
56
|
+
const uri = match[2];
|
|
57
|
+
const lineStart = block.lastIndexOf('\n', match.index) + 1;
|
|
58
|
+
const lineEnd = block.indexOf(';', match.index);
|
|
59
|
+
const snippet = lineEnd >= 0
|
|
60
|
+
? block.slice(lineStart, lineEnd + 1).trim()
|
|
61
|
+
: block.slice(lineStart, match.index + 120).trim();
|
|
62
|
+
// Inline ->middleware(...) on this specific route
|
|
63
|
+
const inlineMatch = snippet.match(/->middleware\s*\(\s*\[?([^\])\n]+)\]?\s*\)/);
|
|
64
|
+
const inlineMiddleware = inlineMatch ? extractMiddlewareList(inlineMatch[1]) : [];
|
|
65
|
+
const allMiddleware = [...inheritedMiddleware, ...inlineMiddleware];
|
|
66
|
+
const beforeMatch = block.slice(0, match.index);
|
|
67
|
+
const line = (beforeMatch.match(/\n/g) ?? []).length + 1;
|
|
68
|
+
routes.push({ method, uri, line, snippet, allMiddleware });
|
|
69
|
+
}
|
|
70
|
+
return routes;
|
|
71
|
+
}
|
|
72
|
+
function parseRouteGroups(content) {
|
|
73
|
+
const groups = [];
|
|
74
|
+
const groupSpans = [];
|
|
75
|
+
// Extract Route::middleware(...)->group(...) blocks
|
|
76
|
+
const groupRegex = /Route::middleware\s*\(([^)]+)\)\s*->group\s*\(\s*function\s*\(\s*\)\s*\{([\s\S]*?)\}\s*\)/g;
|
|
77
|
+
let groupMatch;
|
|
78
|
+
while ((groupMatch = groupRegex.exec(content)) !== null) {
|
|
79
|
+
groupSpans.push({ start: groupMatch.index, end: groupMatch.index + groupMatch[0].length });
|
|
80
|
+
const middlewareList = extractMiddlewareList(groupMatch[1]);
|
|
81
|
+
const groupBody = groupMatch[2];
|
|
82
|
+
const routes = parseRoutesInBlock(groupBody, middlewareList);
|
|
83
|
+
groups.push({ middleware: middlewareList, routes });
|
|
84
|
+
}
|
|
85
|
+
// Top-level routes: strip out group blocks first to avoid double-counting
|
|
86
|
+
let topLevelContent = content;
|
|
87
|
+
for (const span of [...groupSpans].reverse()) {
|
|
88
|
+
topLevelContent =
|
|
89
|
+
topLevelContent.slice(0, span.start) + ' '.repeat(span.end - span.start) + topLevelContent.slice(span.end);
|
|
90
|
+
}
|
|
91
|
+
const topLevelRoutes = parseRoutesInBlock(topLevelContent, []);
|
|
92
|
+
if (topLevelRoutes.length > 0) {
|
|
93
|
+
groups.push({ middleware: [], routes: topLevelRoutes });
|
|
94
|
+
}
|
|
95
|
+
return groups;
|
|
96
|
+
}
|
|
97
|
+
export class RateLimitAnalyzer {
|
|
98
|
+
name = ANALYZER_NAME;
|
|
99
|
+
description = 'Detects sensitive endpoints (auth, API) missing rate limiting middleware';
|
|
100
|
+
async analyze(files, _config) {
|
|
101
|
+
const findings = [];
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
if (!isRouteFile(file.path))
|
|
104
|
+
continue;
|
|
105
|
+
const groups = parseRouteGroups(file.content);
|
|
106
|
+
const isApiFile = file.path.includes('api');
|
|
107
|
+
for (const group of groups) {
|
|
108
|
+
for (const route of group.routes) {
|
|
109
|
+
const allMiddleware = [...group.middleware, ...route.allMiddleware];
|
|
110
|
+
if (hasThrottle(allMiddleware))
|
|
111
|
+
continue;
|
|
112
|
+
if (isAuthEndpoint(route.uri)) {
|
|
113
|
+
findings.push({
|
|
114
|
+
id: FINDING_AUTH_NO_THROTTLE,
|
|
115
|
+
severity: Severity.High,
|
|
116
|
+
analyzer: ANALYZER_NAME,
|
|
117
|
+
title: `Auth endpoint \`${route.uri}\` has no rate limiting`,
|
|
118
|
+
description: `The authentication-sensitive route \`${route.method} ${route.uri}\` has no ` +
|
|
119
|
+
`\`throttle:\` middleware. Without rate limiting, this endpoint is vulnerable to ` +
|
|
120
|
+
`brute-force attacks and credential stuffing.`,
|
|
121
|
+
file: file.path,
|
|
122
|
+
line: route.line,
|
|
123
|
+
recommendation: `Add \`throttle:5,1\` (or stricter) middleware: ` +
|
|
124
|
+
`\`->middleware('throttle:5,1')\`. For auth routes, consider a named limiter ` +
|
|
125
|
+
`with \`RateLimiter::for('login', ...)\`.`,
|
|
126
|
+
cwe: 'CWE-307',
|
|
127
|
+
snippet: route.snippet.slice(0, 200),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else if (isApiFile || /^\/?(api)\//.test(route.uri)) {
|
|
131
|
+
findings.push({
|
|
132
|
+
id: FINDING_API_NO_THROTTLE,
|
|
133
|
+
severity: Severity.Medium,
|
|
134
|
+
analyzer: ANALYZER_NAME,
|
|
135
|
+
title: `API endpoint \`${route.uri}\` has no rate limiting`,
|
|
136
|
+
description: `The API route \`${route.method} ${route.uri}\` has no \`throttle:\` middleware. ` +
|
|
137
|
+
`Unprotected API endpoints can be abused by scrapers or automated bots.`,
|
|
138
|
+
file: file.path,
|
|
139
|
+
line: route.line,
|
|
140
|
+
recommendation: `Add \`throttle:60,1\` middleware to this route or the enclosing route group.`,
|
|
141
|
+
cwe: 'CWE-770',
|
|
142
|
+
snippet: route.snippet.slice(0, 200),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return findings;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=RateLimitAnalyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimitAnalyzer.js","sourceRoot":"","sources":["../../src/analyzers/RateLimitAnalyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Analyzer, Finding, HavocConfig, ParsedFile } from '../types/index.js';
|
|
2
|
+
export declare class SessionSecurityAnalyzer implements Analyzer {
|
|
3
|
+
readonly name = "SessionSecurityAnalyzer";
|
|
4
|
+
readonly description = "Analyzes session and cookie security configuration for vulnerabilities";
|
|
5
|
+
analyze(files: ParsedFile[], _config: HavocConfig): Promise<Finding[]>;
|
|
6
|
+
private analyzeSessionConfig;
|
|
7
|
+
private analyzeCsrfExclusions;
|
|
8
|
+
private analyzeCookieEncryption;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=SessionSecurityAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionSecurityAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzers/SessionSecurityAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAY,MAAM,mBAAmB,CAAC;AA8FzF,qBAAa,uBAAwB,YAAW,QAAQ;IACtD,QAAQ,CAAC,IAAI,6BAAiB;IAC9B,QAAQ,CAAC,WAAW,4EAA4E;IAE1F,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAmB5E,OAAO,CAAC,oBAAoB;IAwI5B,OAAO,CAAC,qBAAqB;IAuD7B,OAAO,CAAC,uBAAuB;CAiChC"}
|