@hoanghainh/komagu 1.8.0 → 2.0.1
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 +20 -0
- package/README.md +1 -1
- package/assess/lsa/CHANGELOG.md +6 -0
- package/assess/lsa/VERSION +1 -1
- package/assess/lsa/adapters/build_adapters.py +16 -8
- package/assess/lsa/adapters/codex/README.md +1 -1
- package/assess/lsa/adapters/generic/README.md +1 -1
- package/assess/lsa/examples/eval-run-4/ground-truth.json +823 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/ai-run-stats.json +58 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/coverage.json +85 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings/priv-privilege-001.json +822 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings/priv-privilege-002.json +393 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings.json +4477 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/score.json +120 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/ai-run-stats.json +55 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/coverage.json +50 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings/priv-privilege-001.json +613 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings/priv-privilege-002.json +287 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings.json +4870 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/score.json +107 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/ai-run-stats.json +58 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/coverage.json +92 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings/priv-privilege-001.json +645 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings/priv-privilege-002.json +222 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings.json +4859 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/score.json +121 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/coverage.json +111 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings/priv-privilege-001.json +6 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings/priv-privilege-002.json +344 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings.json +3544 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/score.json +123 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/ai-run-stats.json +50 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/coverage.json +42 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings/priv-privilege-001.json +540 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings/priv-privilege-002.json +238 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings.json +4383 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings.sarif +8729 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/report.ja.md +1911 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/report.md +1911 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/score.json +101 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/stack-profile.json +226 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/ai-run-stats.json +54 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/coverage.json +66 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings/priv-privilege-001.json +440 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings/priv-privilege-002.json +229 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings.json +3424 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/score.json +120 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/ai-run-stats.json +88 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/coverage.json +36 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings/priv-privilege-001.json +530 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings/priv-privilege-002.json +248 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings.json +4766 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings.sarif +9326 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/report.ja.md +2011 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/report.md +2011 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/score.json +80 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/stack-profile.json +226 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/ai-run-stats.json +83 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/coverage.json +44 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/findings/priv-privilege-001.json +579 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/findings/priv-privilege-002.json +268 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/findings.json +3913 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/score.json +94 -0
- package/assess/lsa/examples/eval-run-5/ground-truth.json +823 -0
- package/assess/lsa/knowledge/severity-and-cwe.md +1 -1
- package/assess/lsa/prompts/35-privilege-auditor.md +3 -1
- package/assess/lsa/scripts/baseline.py +5 -2
- package/assess/lsa/scripts/findings_to_sarif.py +24 -1
- package/assess/lsa/tests/EVAL-RESULTS.md +156 -0
- package/assess/lsa/tests/score_findings.py +58 -0
- package/assess/lsa/tests/test_adapters.py +21 -2
- package/assess/lsa/tests/test_baseline.py +8 -0
- package/assess/lsa/tests/test_contract_groundwork.py +7 -7
- package/assess/lsa/tests/test_eval_run_4.py +73 -0
- package/assess/lsa/tests/test_eval_run_5.py +73 -0
- package/assess/lsa/tests/test_privilege_tier2.py +30 -0
- package/operate/ai/anchoring.py +130 -0
- package/operate/ai/client.py +13 -0
- package/operate/ai/context.py +105 -0
- package/operate/ai/runner.py +197 -69
- package/operate/cli/__init__.py +1 -1
- package/operate/cli/commands/scan.py +3 -8
- package/operate/cli/commands/test.py +1 -1
- package/operate/cli/env.py +4 -20
- package/operate/cross_user_tester/persona_matrix.py +5 -1
- package/operate/devsecops/pipeline.py +5 -5
- package/operate/exploit/generator.py +5 -1
- package/operate/lsa_sarif/lsa_to_sarif.py +24 -2
- package/operate/lsa_sarif/sarif_findings.py +96 -0
- package/operate/remediator/runner.py +1 -5
- package/operate/remediator/strategies.py +1 -1
- package/operate/tests/test_ai_anchoring.py +184 -0
- package/operate/tests/test_cli_baseline.py +1 -9
- package/operate/tests/test_cli_env.py +8 -29
- package/operate/tests/test_cross_user_pipeline.py +2 -2
- package/operate/tests/test_exploit_and_virtual_patch.py +3 -3
- package/operate/tests/test_headless_ai.py +148 -1
- package/operate/tests/test_npm_package.py +2 -16
- package/operate/tests/test_sarif_from_scan.py +156 -0
- package/package.json +2 -4
- package/remediate/authz/README.md +1 -1
- package/remediate/authz/dotnet/src/Komagu.Security.Authorization/DefaultKomaguAuthorizationService.cs +1 -1
- package/remediate/authz/dotnet/src/Komagu.Security.Authorization/SecurityAuditEvent.cs +2 -2
- package/remediate/authz/golang/authz.go +1 -1
- package/remediate/authz/nodejs/src/service.js +1 -1
- package/remediate/authz/nodejs/tests/test-authz.js +1 -1
- package/remediate/authz/patcher.py +3 -4
- package/remediate/authz/python/komagu_authz/models.py +2 -2
- package/remediate/authz/python/tests/test_fastapi_adapter.py +1 -1
- package/remediate/authz/python/tests/test_fastapi_d5_crosstest.py +1 -1
- package/remediate/authz/python/tests/test_python_authz.py +3 -3
- package/remediate/authz/spring/src/main/kotlin/io/komagu/security/authorization/DefaultKomaguSecurityManager.kt +1 -1
- package/remediate/authz/spring/src/main/kotlin/io/komagu/security/authorization/SecurityAuditEvent.kt +1 -2
- package/remediate/authz/tests/test_bola_patcher.py +1 -1
- package/remediate/edge/coraza/rules/coraza-crs-custom.conf +3 -4
- package/remediate/edge/coraza/virtual_patcher.py +4 -6
- package/remediate/edge/docker-compose.yml +1 -1
- package/remediate/ekyc/server/jpki_verifier.py +2 -8
- package/remediate/ekyc/server/tokenization.py +2 -8
- package/remediate/identity/README.md +5 -5
- package/remediate/identity/dotnet/src/Komagu.Security.Identity/KomaguOidcOptions.cs +2 -2
- package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TestTokenGenerator.cs +1 -1
- package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TokenValidationTests.cs +1 -1
- package/remediate/identity/python/komagu_identity/oidc_validator.py +2 -8
- package/remediate/identity/python/tests/test_oidc_validator.py +1 -1
- package/remediate/identity/spring/src/main/kotlin/io/komagu/security/identity/KomaguOidcProperties.kt +2 -2
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/KomaguIdentityAutoConfigurationTest.kt +2 -2
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TestJwtFactory.kt +1 -1
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TokenValidationIntegrationTest.kt +1 -1
- package/remediate/identity/test-env/realm-komagu.json +1 -1
- package/remediate/legacy_shims/coldfusion/KomaguSecurityShim.cfc +5 -5
- package/remediate/query/README.md +1 -1
- package/remediate/query/dotnet/src/Komagu.Security.Query.Analyzers/DiagnosticIds.cs +1 -1
- package/remediate/query/dotnet/src/Komagu.Security.Query.Analyzers/Komagu.Security.Query.Analyzers.csproj +1 -1
- package/remediate/query/spring/src/main/kotlin/io/komagu/security/query/detekt/KomaguRuleSetProvider.kt +1 -1
- package/remediate/resilience/dr/dr_manager.py +2 -8
- package/remediate/resilience/dr/runbook.md +2 -2
- package/remediate/resilience/terraform/variables.tf +1 -1
- package/remediate/tenancy/audit/audit_trail.py +2 -8
- package/remediate/tenancy/crypto/byok_manager.py +2 -8
- package/remediate/tenancy/middleware/tenant_context.py +2 -8
- package/remediate/tenancy/sql/rls_isolation.sql +11 -11
- package/remediate/tenancy/sql/schema_per_tenant.sql +7 -7
- package/remediate/tenancy/test_sql_postgres.py +1 -1
- package/remediate/tenancy/tests/test_tenancy.py +4 -4
- package/bin/komainu +0 -16
- package/bin/komainu-legacy.js +0 -16
- package/remediate/ekyc/tests/test_legacy_env.py +0 -33
- package/remediate/identity/python/tests/test_legacy_env.py +0 -33
- package/remediate/identity/spring/src/main/kotlin/io/komagu/security/identity/LegacyKomainuPropertiesPostProcessor.kt +0 -60
- package/remediate/identity/spring/src/main/resources/META-INF/spring.factories +0 -2
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/LegacyKomainuPropertiesPostProcessorTest.kt +0 -86
- package/remediate/resilience/tests/test_legacy_env.py +0 -33
- package/remediate/tenancy/tests/test_legacy_env.py +0 -49
|
@@ -0,0 +1,4477 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generated_by": "lsa merge_findings.py 0.3.1",
|
|
3
|
+
"summary": {
|
|
4
|
+
"total_reported": 84,
|
|
5
|
+
"active": 74,
|
|
6
|
+
"rejected_by_verifier": 10,
|
|
7
|
+
"by_severity": {
|
|
8
|
+
"critical": 34,
|
|
9
|
+
"high": 40,
|
|
10
|
+
"medium": 0,
|
|
11
|
+
"low": 0,
|
|
12
|
+
"info": 0
|
|
13
|
+
},
|
|
14
|
+
"by_category": {
|
|
15
|
+
"bola": 40,
|
|
16
|
+
"sql_injection": 17,
|
|
17
|
+
"nosql_injection": 3,
|
|
18
|
+
"bfla": 12,
|
|
19
|
+
"mass_assignment": 2
|
|
20
|
+
},
|
|
21
|
+
"by_status": {
|
|
22
|
+
"unverified": 11,
|
|
23
|
+
"confirmed": 63,
|
|
24
|
+
"likely": 0,
|
|
25
|
+
"rejected": 10,
|
|
26
|
+
"needs_review": 0
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"findings": [
|
|
30
|
+
{
|
|
31
|
+
"finding_id": "BFLA-0003-1",
|
|
32
|
+
"category": "bfla",
|
|
33
|
+
"subtype": "missing_role_check",
|
|
34
|
+
"title": "Classic‑ASP member deletion without role check",
|
|
35
|
+
"route_id": "R-0003",
|
|
36
|
+
"endpoint": {
|
|
37
|
+
"method": "GET|POST",
|
|
38
|
+
"path": "/classic-asp/shop/member.asp?mode=delete"
|
|
39
|
+
},
|
|
40
|
+
"location": {
|
|
41
|
+
"file": "classic-asp/shop/member.asp",
|
|
42
|
+
"start_line": 1,
|
|
43
|
+
"end_line": 11
|
|
44
|
+
},
|
|
45
|
+
"evidence": [
|
|
46
|
+
{
|
|
47
|
+
"file": "classic-asp/shop/member.asp",
|
|
48
|
+
"line": 1,
|
|
49
|
+
"code": "<!--#include file=\"inc/auth.asp\"-->",
|
|
50
|
+
"why": "auth guard only checks session, no role"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"file": "classic-asp/shop/member.asp",
|
|
54
|
+
"line": 10,
|
|
55
|
+
"code": "conn.Execute \"DELETE FROM members WHERE member_id = \" & Request.QueryString(\"member_id\")",
|
|
56
|
+
"why": "privileged action, no role check"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"attack_scenario": "A logged‑in user can delete any member by supplying a member_id in the query string.",
|
|
60
|
+
"impact": "Any authenticated user can delete arbitrary members, including administrators.",
|
|
61
|
+
"severity": "critical",
|
|
62
|
+
"confidence": "high",
|
|
63
|
+
"cwe": [
|
|
64
|
+
"CWE-285"
|
|
65
|
+
],
|
|
66
|
+
"owasp": [
|
|
67
|
+
"API5:2023"
|
|
68
|
+
],
|
|
69
|
+
"recommendation": "Add a server‑side role check that ensures the current user has the 'Admin' role before executing the delete.",
|
|
70
|
+
"fix_example": "If Not CheckRole(\"Admin\") Then Response.Status = \"403 Forbidden\" : Response.End",
|
|
71
|
+
"status": "confirmed",
|
|
72
|
+
"verification": {
|
|
73
|
+
"verdict": "confirmed",
|
|
74
|
+
"reason_code": "traced",
|
|
75
|
+
"checked": [
|
|
76
|
+
"classic-asp/shop/member.asp lines 1-11",
|
|
77
|
+
"classic-asp/inc/auth.asp for session check",
|
|
78
|
+
"classic-asp/shop/member.asp for delete query"
|
|
79
|
+
],
|
|
80
|
+
"control_found": null,
|
|
81
|
+
"notes": "The included `auth.asp` only verifies that a session exists; it does not enforce a role. The delete statement uses the `member_id` from the query string without any role validation. Any authenticated user can delete any member, including administrators. The BFLA is confirmed.",
|
|
82
|
+
"suggested_subtype": null,
|
|
83
|
+
"suggested_cwe": null
|
|
84
|
+
},
|
|
85
|
+
"consensus": {
|
|
86
|
+
"score": 0.95,
|
|
87
|
+
"agreement_level": "unanimous",
|
|
88
|
+
"voted_models": [
|
|
89
|
+
"gpt-oss:20b"
|
|
90
|
+
],
|
|
91
|
+
"rationale": "No role check found in authentication guard."
|
|
92
|
+
},
|
|
93
|
+
"fingerprint": "kfp1:03b63bd8c1464dd4d2026fe65585a4541f0134b2b03f8d84f893ffa59f07aa9b",
|
|
94
|
+
"baseline_state": "new"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"finding_id": "BFLA-0007-1",
|
|
98
|
+
"category": "bfla",
|
|
99
|
+
"subtype": "missing_role_check",
|
|
100
|
+
"title": "ASP.NET MVC DeleteUser action lacks admin role check",
|
|
101
|
+
"route_id": "R-0007",
|
|
102
|
+
"endpoint": {
|
|
103
|
+
"method": "POST",
|
|
104
|
+
"path": "/Admin/DeleteUser"
|
|
105
|
+
},
|
|
106
|
+
"location": {
|
|
107
|
+
"file": "Web/Controllers/AdminController.cs",
|
|
108
|
+
"start_line": 6,
|
|
109
|
+
"end_line": 18
|
|
110
|
+
},
|
|
111
|
+
"evidence": [
|
|
112
|
+
{
|
|
113
|
+
"file": "Web/Controllers/AdminController.cs",
|
|
114
|
+
"line": 6,
|
|
115
|
+
"code": "[Authorize]",
|
|
116
|
+
"why": "ensures authentication only"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"file": "Web/Controllers/AdminController.cs",
|
|
120
|
+
"line": 12,
|
|
121
|
+
"code": "public ActionResult DeleteUser(int id)",
|
|
122
|
+
"why": "no role check on method"
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
"attack_scenario": "Any authenticated user can delete any user account.",
|
|
126
|
+
"impact": "Privilege escalation to delete arbitrary user accounts.",
|
|
127
|
+
"severity": "critical",
|
|
128
|
+
"confidence": "high",
|
|
129
|
+
"cwe": [
|
|
130
|
+
"CWE-285"
|
|
131
|
+
],
|
|
132
|
+
"owasp": [
|
|
133
|
+
"API5:2023"
|
|
134
|
+
],
|
|
135
|
+
"recommendation": "Add [Authorize(Roles = \"Admin\")] to the DeleteUser action.",
|
|
136
|
+
"fix_example": "[Authorize(Roles = \"Admin\")] public ActionResult DeleteUser(int id) { … }",
|
|
137
|
+
"status": "confirmed",
|
|
138
|
+
"verification": {
|
|
139
|
+
"verdict": "confirmed",
|
|
140
|
+
"reason_code": "traced",
|
|
141
|
+
"checked": [
|
|
142
|
+
"Web/Controllers/AdminController.cs lines 6-18",
|
|
143
|
+
"Web/Controllers/AdminController.cs for [Authorize] attribute",
|
|
144
|
+
"Web/Controllers/AdminController.cs for DeleteUser method",
|
|
145
|
+
"Web/FilterConfig.cs for global filters"
|
|
146
|
+
],
|
|
147
|
+
"control_found": null,
|
|
148
|
+
"notes": "The controller method is protected only by `[Authorize]`, which ensures authentication but not a specific role. No global filter or other configuration restricts the action to the Admin role. Therefore any authenticated user can invoke DeleteUser, confirming the BFLA.",
|
|
149
|
+
"suggested_subtype": null,
|
|
150
|
+
"suggested_cwe": null
|
|
151
|
+
},
|
|
152
|
+
"consensus": {
|
|
153
|
+
"score": 0.95,
|
|
154
|
+
"agreement_level": "unanimous",
|
|
155
|
+
"voted_models": [
|
|
156
|
+
"gpt-oss:20b"
|
|
157
|
+
],
|
|
158
|
+
"rationale": "No admin‑role enforcement detected."
|
|
159
|
+
},
|
|
160
|
+
"fingerprint": "kfp1:036f864a17d084df6648dbc52401562374be98589fdff40adba7bcc100f9d60f",
|
|
161
|
+
"baseline_state": "new"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"finding_id": "BFLA-0009-1",
|
|
165
|
+
"category": "bfla",
|
|
166
|
+
"subtype": "role_from_request",
|
|
167
|
+
"title": "ASP.NET MVC Export action trusts request query for admin access",
|
|
168
|
+
"route_id": "R-0009",
|
|
169
|
+
"endpoint": {
|
|
170
|
+
"method": "GET",
|
|
171
|
+
"path": "/Admin/Export"
|
|
172
|
+
},
|
|
173
|
+
"location": {
|
|
174
|
+
"file": "Web/Controllers/AdminController.cs",
|
|
175
|
+
"start_line": 30,
|
|
176
|
+
"end_line": 37
|
|
177
|
+
},
|
|
178
|
+
"evidence": [
|
|
179
|
+
{
|
|
180
|
+
"file": "Web/Controllers/AdminController.cs",
|
|
181
|
+
"line": 32,
|
|
182
|
+
"code": "if (Request.QueryString[\"isAdmin\"] == \"true\") { … }",
|
|
183
|
+
"why": "role derived from client request"
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"attack_scenario": "A logged‑in user can set ?isAdmin=true to export all users.",
|
|
187
|
+
"impact": "Unauthorized data export of all user records.",
|
|
188
|
+
"severity": "critical",
|
|
189
|
+
"confidence": "high",
|
|
190
|
+
"cwe": [
|
|
191
|
+
"CWE-269",
|
|
192
|
+
"CWE-285"
|
|
193
|
+
],
|
|
194
|
+
"owasp": [
|
|
195
|
+
"API5:2023"
|
|
196
|
+
],
|
|
197
|
+
"recommendation": "Remove reliance on client query and enforce server‑side role check.",
|
|
198
|
+
"fix_example": "if (!User.IsInRole(\"Admin\")) { return new HttpStatusCodeResult(403); }",
|
|
199
|
+
"status": "confirmed",
|
|
200
|
+
"verification": {
|
|
201
|
+
"verdict": "confirmed",
|
|
202
|
+
"reason_code": "traced",
|
|
203
|
+
"checked": [
|
|
204
|
+
"Web/Controllers/AdminController.cs lines 30-37",
|
|
205
|
+
"Web/Controllers/AdminController.cs for Request.QueryString check",
|
|
206
|
+
"Web/FilterConfig.cs for global filters"
|
|
207
|
+
],
|
|
208
|
+
"control_found": null,
|
|
209
|
+
"notes": "The action trusts a query string parameter `isAdmin` to grant export privileges. No role check is performed, and no global filter restricts the route to administrators. An attacker can set `?isAdmin=true` and trigger the export, confirming the BFLA.",
|
|
210
|
+
"suggested_subtype": null,
|
|
211
|
+
"suggested_cwe": null
|
|
212
|
+
},
|
|
213
|
+
"consensus": {
|
|
214
|
+
"score": 0.95,
|
|
215
|
+
"agreement_level": "unanimous",
|
|
216
|
+
"voted_models": [
|
|
217
|
+
"gpt-oss:20b"
|
|
218
|
+
],
|
|
219
|
+
"rationale": "No server‑side role enforcement found."
|
|
220
|
+
},
|
|
221
|
+
"fingerprint": "kfp1:c999e3c538ddc0476bb4d85071163f76d9648bb04fcb7f6f3d7aa2d5bff24963",
|
|
222
|
+
"baseline_state": "new"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"finding_id": "BFLA-0030-1",
|
|
226
|
+
"category": "bfla",
|
|
227
|
+
"subtype": "missing_role_check",
|
|
228
|
+
"title": "Express admin delete user route missing admin guard",
|
|
229
|
+
"route_id": "R-0030",
|
|
230
|
+
"endpoint": {
|
|
231
|
+
"method": "POST",
|
|
232
|
+
"path": "/admin/users/:id/delete"
|
|
233
|
+
},
|
|
234
|
+
"location": {
|
|
235
|
+
"file": "routes/admin.js",
|
|
236
|
+
"start_line": 8,
|
|
237
|
+
"end_line": 10
|
|
238
|
+
},
|
|
239
|
+
"evidence": [
|
|
240
|
+
{
|
|
241
|
+
"file": "routes/admin.js",
|
|
242
|
+
"line": 8,
|
|
243
|
+
"code": "router.post('/admin/users/:id/delete', ensureLoggedIn, function (req, res) { … }",
|
|
244
|
+
"why": "only ensures login, no admin check"
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"attack_scenario": "Any logged‑in user can delete any user account.",
|
|
248
|
+
"impact": "Privilege escalation to delete arbitrary user accounts.",
|
|
249
|
+
"severity": "critical",
|
|
250
|
+
"confidence": "high",
|
|
251
|
+
"cwe": [
|
|
252
|
+
"CWE-285"
|
|
253
|
+
],
|
|
254
|
+
"owasp": [
|
|
255
|
+
"API5:2023"
|
|
256
|
+
],
|
|
257
|
+
"recommendation": "Add an admin guard middleware before the handler.",
|
|
258
|
+
"fix_example": "router.post('/admin/users/:id/delete', ensureLoggedIn, requireAdmin, function (req, res) { … }",
|
|
259
|
+
"status": "confirmed",
|
|
260
|
+
"verification": {
|
|
261
|
+
"verdict": "confirmed",
|
|
262
|
+
"reason_code": "traced",
|
|
263
|
+
"checked": [
|
|
264
|
+
"routes/admin.js",
|
|
265
|
+
"middleware/auth.js",
|
|
266
|
+
"app.js"
|
|
267
|
+
],
|
|
268
|
+
"control_found": null,
|
|
269
|
+
"notes": "The route handler only uses ensureLoggedIn; no admin role check is performed in the handler, middleware, or global app configuration. The /admin/users/:id/delete endpoint is reachable via POST and can be invoked by any authenticated user.",
|
|
270
|
+
"suggested_subtype": null,
|
|
271
|
+
"suggested_cwe": null
|
|
272
|
+
},
|
|
273
|
+
"consensus": {
|
|
274
|
+
"score": 0.95,
|
|
275
|
+
"agreement_level": "unanimous",
|
|
276
|
+
"voted_models": [
|
|
277
|
+
"gpt-oss:20b"
|
|
278
|
+
],
|
|
279
|
+
"rationale": "No admin guard found in any relevant file."
|
|
280
|
+
},
|
|
281
|
+
"fingerprint": "kfp1:3f5f36c22e06a8964922e65bc1f36c29f2ee48c28b46a56c9b680fdbfdf4f267",
|
|
282
|
+
"baseline_state": "new"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"finding_id": "BFLA-0032-1",
|
|
286
|
+
"category": "bfla",
|
|
287
|
+
"subtype": "role_from_request",
|
|
288
|
+
"title": "Express export route trusts client query for admin access",
|
|
289
|
+
"route_id": "R-0032",
|
|
290
|
+
"endpoint": {
|
|
291
|
+
"method": "GET",
|
|
292
|
+
"path": "/reports/export"
|
|
293
|
+
},
|
|
294
|
+
"location": {
|
|
295
|
+
"file": "routes/admin.js",
|
|
296
|
+
"start_line": 16,
|
|
297
|
+
"end_line": 18
|
|
298
|
+
},
|
|
299
|
+
"evidence": [
|
|
300
|
+
{
|
|
301
|
+
"file": "routes/admin.js",
|
|
302
|
+
"line": 17,
|
|
303
|
+
"code": "if (req.query.isAdmin === 'true') { res.json([]); }",
|
|
304
|
+
"why": "role derived from client query"
|
|
305
|
+
}
|
|
306
|
+
],
|
|
307
|
+
"attack_scenario": "Any logged‑in user can export all user data by setting ?isAdmin=true.",
|
|
308
|
+
"impact": "Unauthorized data export of all user records.",
|
|
309
|
+
"severity": "critical",
|
|
310
|
+
"confidence": "high",
|
|
311
|
+
"cwe": [
|
|
312
|
+
"CWE-269",
|
|
313
|
+
"CWE-285"
|
|
314
|
+
],
|
|
315
|
+
"owasp": [
|
|
316
|
+
"API5:2023"
|
|
317
|
+
],
|
|
318
|
+
"recommendation": "Remove client‑controlled isAdmin flag and enforce server‑side role check.",
|
|
319
|
+
"fix_example": "if (!req.user.isAdmin) { return res.sendStatus(403); }",
|
|
320
|
+
"status": "confirmed",
|
|
321
|
+
"verification": {
|
|
322
|
+
"verdict": "confirmed",
|
|
323
|
+
"reason_code": "traced",
|
|
324
|
+
"checked": [
|
|
325
|
+
"routes/admin.js",
|
|
326
|
+
"middleware/auth.js"
|
|
327
|
+
],
|
|
328
|
+
"control_found": null,
|
|
329
|
+
"notes": "The handler checks only req.query.isAdmin and does not verify the user’s role. No server‑side role enforcement exists for /reports/export.",
|
|
330
|
+
"suggested_subtype": null,
|
|
331
|
+
"suggested_cwe": null
|
|
332
|
+
},
|
|
333
|
+
"consensus": {
|
|
334
|
+
"score": 0.95,
|
|
335
|
+
"agreement_level": "unanimous",
|
|
336
|
+
"voted_models": [
|
|
337
|
+
"gpt-oss:20b"
|
|
338
|
+
],
|
|
339
|
+
"rationale": "Client‑controlled flag is the sole authorization check."
|
|
340
|
+
},
|
|
341
|
+
"fingerprint": "kfp1:321ec7d7fe9918558d81dd864e71a857735c3d815d261033a93c07a9946a06e3",
|
|
342
|
+
"baseline_state": "new"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"finding_id": "BFLA-0057-1",
|
|
346
|
+
"category": "bfla",
|
|
347
|
+
"subtype": "missing_role_check",
|
|
348
|
+
"title": "PHP admin view user without role check",
|
|
349
|
+
"route_id": "R-0057",
|
|
350
|
+
"endpoint": {
|
|
351
|
+
"method": "GET",
|
|
352
|
+
"path": "/php-plain/admin.php?action=view"
|
|
353
|
+
},
|
|
354
|
+
"location": {
|
|
355
|
+
"file": "php-plain/admin.php",
|
|
356
|
+
"start_line": 5,
|
|
357
|
+
"end_line": 9
|
|
358
|
+
},
|
|
359
|
+
"evidence": [
|
|
360
|
+
{
|
|
361
|
+
"file": "php-plain/admin.php",
|
|
362
|
+
"line": 6,
|
|
363
|
+
"code": "case 'view': $uid = (int)$_GET['uid']; $r = mysql_query(\"SELECT * FROM users WHERE id = \" . $uid);",
|
|
364
|
+
"why": "privileged action, no role check"
|
|
365
|
+
}
|
|
366
|
+
],
|
|
367
|
+
"attack_scenario": "A logged‑in user can view any user record by supplying a uid.",
|
|
368
|
+
"impact": "Unauthorized disclosure of arbitrary user data.",
|
|
369
|
+
"severity": "critical",
|
|
370
|
+
"confidence": "high",
|
|
371
|
+
"cwe": [
|
|
372
|
+
"CWE-285"
|
|
373
|
+
],
|
|
374
|
+
"owasp": [
|
|
375
|
+
"API5:2023"
|
|
376
|
+
],
|
|
377
|
+
"recommendation": "Add a role check that ensures the current user is an administrator before executing the query.",
|
|
378
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
379
|
+
"status": "confirmed",
|
|
380
|
+
"verification": {
|
|
381
|
+
"verdict": "confirmed",
|
|
382
|
+
"reason_code": "traced",
|
|
383
|
+
"checked": [
|
|
384
|
+
"php-plain/admin.php",
|
|
385
|
+
"inc/auth.php"
|
|
386
|
+
],
|
|
387
|
+
"control_found": null,
|
|
388
|
+
"notes": "auth.php only verifies a session; it does not check for an admin role. The view action directly queries the database using a user‑supplied uid.",
|
|
389
|
+
"suggested_subtype": null,
|
|
390
|
+
"suggested_cwe": null
|
|
391
|
+
},
|
|
392
|
+
"consensus": {
|
|
393
|
+
"score": 0.95,
|
|
394
|
+
"agreement_level": "unanimous",
|
|
395
|
+
"voted_models": [
|
|
396
|
+
"gpt-oss:20b"
|
|
397
|
+
],
|
|
398
|
+
"rationale": "No role check before privileged query."
|
|
399
|
+
},
|
|
400
|
+
"fingerprint": "kfp1:24463839a62154db0eed0999f7fe58b5d35eaa3c951d9c8591ba8d1589c1af4d",
|
|
401
|
+
"baseline_state": "new"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"finding_id": "BFLA-0058-1",
|
|
405
|
+
"category": "bfla",
|
|
406
|
+
"subtype": "missing_role_check",
|
|
407
|
+
"title": "PHP admin delete user without role check",
|
|
408
|
+
"route_id": "R-0058",
|
|
409
|
+
"endpoint": {
|
|
410
|
+
"method": "POST",
|
|
411
|
+
"path": "/php-plain/admin.php?action=delete"
|
|
412
|
+
},
|
|
413
|
+
"location": {
|
|
414
|
+
"file": "php-plain/admin.php",
|
|
415
|
+
"start_line": 10,
|
|
416
|
+
"end_line": 13
|
|
417
|
+
},
|
|
418
|
+
"evidence": [
|
|
419
|
+
{
|
|
420
|
+
"file": "php-plain/admin.php",
|
|
421
|
+
"line": 10,
|
|
422
|
+
"code": "case 'delete': $id = $_POST['id']; mysql_query(\"DELETE FROM users WHERE id = $id\");",
|
|
423
|
+
"why": "privileged action, no role check"
|
|
424
|
+
}
|
|
425
|
+
],
|
|
426
|
+
"attack_scenario": "A logged‑in user can delete any user record by posting an id.",
|
|
427
|
+
"impact": "Privilege escalation to delete arbitrary user accounts.",
|
|
428
|
+
"severity": "critical",
|
|
429
|
+
"confidence": "high",
|
|
430
|
+
"cwe": [
|
|
431
|
+
"CWE-285"
|
|
432
|
+
],
|
|
433
|
+
"owasp": [
|
|
434
|
+
"API5:2023"
|
|
435
|
+
],
|
|
436
|
+
"recommendation": "Add a role check that ensures the current user is an administrator before executing the delete.",
|
|
437
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
438
|
+
"status": "confirmed",
|
|
439
|
+
"verification": {
|
|
440
|
+
"verdict": "confirmed",
|
|
441
|
+
"reason_code": "traced",
|
|
442
|
+
"checked": [
|
|
443
|
+
"php-plain/admin.php",
|
|
444
|
+
"inc/auth.php"
|
|
445
|
+
],
|
|
446
|
+
"control_found": null,
|
|
447
|
+
"notes": "The delete case executes a raw DELETE statement with a POST‑supplied id and no role verification.",
|
|
448
|
+
"suggested_subtype": null,
|
|
449
|
+
"suggested_cwe": null
|
|
450
|
+
},
|
|
451
|
+
"consensus": {
|
|
452
|
+
"score": 0.95,
|
|
453
|
+
"agreement_level": "unanimous",
|
|
454
|
+
"voted_models": [
|
|
455
|
+
"gpt-oss:20b"
|
|
456
|
+
],
|
|
457
|
+
"rationale": "Missing admin guard."
|
|
458
|
+
},
|
|
459
|
+
"fingerprint": "kfp1:bd374da2b69d7107b5c9c01ba84a6528c815d3e7f92fa131887f2760a16d63c8",
|
|
460
|
+
"baseline_state": "new"
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"finding_id": "BFLA-0059-1",
|
|
464
|
+
"category": "bfla",
|
|
465
|
+
"subtype": "client_side_role_check",
|
|
466
|
+
"title": "PHP manage users page uses client‑side role check for delete link",
|
|
467
|
+
"route_id": "R-0059",
|
|
468
|
+
"endpoint": {
|
|
469
|
+
"method": "GET",
|
|
470
|
+
"path": "/php-plain/manage_users.php"
|
|
471
|
+
},
|
|
472
|
+
"location": {
|
|
473
|
+
"file": "php-plain/manage_users.php",
|
|
474
|
+
"start_line": 10,
|
|
475
|
+
"end_line": 12
|
|
476
|
+
},
|
|
477
|
+
"evidence": [
|
|
478
|
+
{
|
|
479
|
+
"file": "php-plain/manage_users.php",
|
|
480
|
+
"line": 11,
|
|
481
|
+
"code": "<?php if ($_SESSION['role'] == 'admin'): ?><li><a href=\"/manage_users.php\">Delete users</a></li><?php endif; ?>",
|
|
482
|
+
"why": "role check only in markup, not enforced server‑side"
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
"attack_scenario": "A logged‑in user can submit a delete request even if the link is not shown.",
|
|
486
|
+
"impact": "Privilege escalation to delete arbitrary user accounts.",
|
|
487
|
+
"severity": "critical",
|
|
488
|
+
"confidence": "high",
|
|
489
|
+
"cwe": [
|
|
490
|
+
"CWE-602",
|
|
491
|
+
"CWE-285"
|
|
492
|
+
],
|
|
493
|
+
"owasp": [
|
|
494
|
+
"API5:2023"
|
|
495
|
+
],
|
|
496
|
+
"recommendation": "Enforce role check on the delete handler, not just in the UI.",
|
|
497
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
498
|
+
"status": "confirmed",
|
|
499
|
+
"verification": {
|
|
500
|
+
"verdict": "confirmed",
|
|
501
|
+
"reason_code": "traced",
|
|
502
|
+
"checked": [
|
|
503
|
+
"php-plain/manage_users.php",
|
|
504
|
+
"inc/auth.php"
|
|
505
|
+
],
|
|
506
|
+
"control_found": null,
|
|
507
|
+
"notes": "The delete link is rendered only for admins, but the delete action itself is handled by admin.php which lacks a server‑side role check.",
|
|
508
|
+
"suggested_subtype": null,
|
|
509
|
+
"suggested_cwe": null
|
|
510
|
+
},
|
|
511
|
+
"consensus": {
|
|
512
|
+
"score": 0.95,
|
|
513
|
+
"agreement_level": "unanimous",
|
|
514
|
+
"voted_models": [
|
|
515
|
+
"gpt-oss:20b"
|
|
516
|
+
],
|
|
517
|
+
"rationale": "Client‑side role check does not protect the endpoint."
|
|
518
|
+
},
|
|
519
|
+
"fingerprint": "kfp1:6463e99d3b628f52938f312ad03c62ebc6e7e667275bc3e1df3f5fd1d2bc4c7f",
|
|
520
|
+
"baseline_state": "new"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"finding_id": "BFLA-0063-1",
|
|
524
|
+
"category": "bfla",
|
|
525
|
+
"subtype": "role_from_request",
|
|
526
|
+
"title": "PHP profile update allows client to set role",
|
|
527
|
+
"route_id": "R-0063",
|
|
528
|
+
"endpoint": {
|
|
529
|
+
"method": "POST",
|
|
530
|
+
"path": "/php-plain/profile.php"
|
|
531
|
+
},
|
|
532
|
+
"location": {
|
|
533
|
+
"file": "php-plain/profile.php",
|
|
534
|
+
"start_line": 6,
|
|
535
|
+
"end_line": 11
|
|
536
|
+
},
|
|
537
|
+
"evidence": [
|
|
538
|
+
{
|
|
539
|
+
"file": "php-plain/profile.php",
|
|
540
|
+
"line": 10,
|
|
541
|
+
"code": "$user->role = $_POST['role'];",
|
|
542
|
+
"why": "role derived from client request"
|
|
543
|
+
}
|
|
544
|
+
],
|
|
545
|
+
"attack_scenario": "A user can change their own role to admin.",
|
|
546
|
+
"impact": "Privilege escalation to admin role.",
|
|
547
|
+
"severity": "critical",
|
|
548
|
+
"confidence": "high",
|
|
549
|
+
"cwe": [
|
|
550
|
+
"CWE-269",
|
|
551
|
+
"CWE-285"
|
|
552
|
+
],
|
|
553
|
+
"owasp": [
|
|
554
|
+
"API5:2023"
|
|
555
|
+
],
|
|
556
|
+
"recommendation": "Remove client‑controlled role assignment and enforce server‑side role check.",
|
|
557
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
558
|
+
"status": "confirmed",
|
|
559
|
+
"verification": {
|
|
560
|
+
"verdict": "confirmed",
|
|
561
|
+
"reason_code": "traced",
|
|
562
|
+
"checked": [
|
|
563
|
+
"php-plain/profile.php",
|
|
564
|
+
"inc/auth.php",
|
|
565
|
+
"inc/db.php"
|
|
566
|
+
],
|
|
567
|
+
"control_found": null,
|
|
568
|
+
"notes": "The POST handler assigns $_POST['role'] directly to the user object without verifying the caller’s role.",
|
|
569
|
+
"suggested_subtype": null,
|
|
570
|
+
"suggested_cwe": null
|
|
571
|
+
},
|
|
572
|
+
"consensus": {
|
|
573
|
+
"score": 0.95,
|
|
574
|
+
"agreement_level": "unanimous",
|
|
575
|
+
"voted_models": [
|
|
576
|
+
"gpt-oss:20b"
|
|
577
|
+
],
|
|
578
|
+
"rationale": "Client‑controlled role assignment."
|
|
579
|
+
},
|
|
580
|
+
"fingerprint": "kfp1:22d11d6a35e29ad3a827771faaa9664d7e9b1ec1018d9f280f58890070dc7dd7",
|
|
581
|
+
"baseline_state": "new"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"finding_id": "BFLA-0066-1",
|
|
585
|
+
"category": "bfla",
|
|
586
|
+
"subtype": "missing_role_check",
|
|
587
|
+
"title": "PHP admin delete user without role check",
|
|
588
|
+
"route_id": "R-0066",
|
|
589
|
+
"endpoint": {
|
|
590
|
+
"method": "POST",
|
|
591
|
+
"path": "/php-plain/admin/delete_user.php"
|
|
592
|
+
},
|
|
593
|
+
"location": {
|
|
594
|
+
"file": "php-plain/admin/delete_user.php",
|
|
595
|
+
"start_line": 1,
|
|
596
|
+
"end_line": 8
|
|
597
|
+
},
|
|
598
|
+
"evidence": [
|
|
599
|
+
{
|
|
600
|
+
"file": "php-plain/admin/delete_user.php",
|
|
601
|
+
"line": 5,
|
|
602
|
+
"code": "$id = (int)$_POST['id']; $stmt = $db->prepare(\"DELETE FROM users WHERE id = ?\");",
|
|
603
|
+
"why": "privileged action, no role check"
|
|
604
|
+
}
|
|
605
|
+
],
|
|
606
|
+
"attack_scenario": "A logged‑in user can delete any user record by posting an id.",
|
|
607
|
+
"impact": "Privilege escalation to delete arbitrary user accounts.",
|
|
608
|
+
"severity": "critical",
|
|
609
|
+
"confidence": "high",
|
|
610
|
+
"cwe": [
|
|
611
|
+
"CWE-285"
|
|
612
|
+
],
|
|
613
|
+
"owasp": [
|
|
614
|
+
"API5:2023"
|
|
615
|
+
],
|
|
616
|
+
"recommendation": "Add a role check that ensures the current user is an administrator before executing the delete.",
|
|
617
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
618
|
+
"status": "confirmed",
|
|
619
|
+
"verification": {
|
|
620
|
+
"verdict": "confirmed",
|
|
621
|
+
"reason_code": "traced",
|
|
622
|
+
"checked": [
|
|
623
|
+
"php-plain/admin/delete_user.php",
|
|
624
|
+
"inc/auth.php",
|
|
625
|
+
"inc/db.php"
|
|
626
|
+
],
|
|
627
|
+
"control_found": null,
|
|
628
|
+
"notes": "The script casts the id to int but performs no role check; auth.php only ensures a session exists.",
|
|
629
|
+
"suggested_subtype": null,
|
|
630
|
+
"suggested_cwe": null
|
|
631
|
+
},
|
|
632
|
+
"consensus": {
|
|
633
|
+
"score": 0.95,
|
|
634
|
+
"agreement_level": "unanimous",
|
|
635
|
+
"voted_models": [
|
|
636
|
+
"gpt-oss:20b"
|
|
637
|
+
],
|
|
638
|
+
"rationale": "No admin guard present."
|
|
639
|
+
},
|
|
640
|
+
"fingerprint": "kfp1:2a567b348f1bc95c2c2471321a21c71d9f8c194752aea2233dee0dc9f2990bcc",
|
|
641
|
+
"baseline_state": "new"
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
"finding_id": "BFLA-0067-1",
|
|
645
|
+
"category": "bfla",
|
|
646
|
+
"subtype": "role_from_request",
|
|
647
|
+
"title": "PHP admin promote user trusts client input for role change",
|
|
648
|
+
"route_id": "R-0067",
|
|
649
|
+
"endpoint": {
|
|
650
|
+
"method": "POST",
|
|
651
|
+
"path": "/php-plain/admin/promote.php"
|
|
652
|
+
},
|
|
653
|
+
"location": {
|
|
654
|
+
"file": "php-plain/admin/promote.php",
|
|
655
|
+
"start_line": 5,
|
|
656
|
+
"end_line": 8
|
|
657
|
+
},
|
|
658
|
+
"evidence": [
|
|
659
|
+
{
|
|
660
|
+
"file": "php-plain/admin/promote.php",
|
|
661
|
+
"line": 5,
|
|
662
|
+
"code": "if ($_POST['is_admin'] == '1') { $stmt = $db->prepare(\"UPDATE users SET role = 'admin' WHERE id = ?\");",
|
|
663
|
+
"why": "role derived from client request"
|
|
664
|
+
}
|
|
665
|
+
],
|
|
666
|
+
"attack_scenario": "A logged‑in user can promote themselves to admin by posting is_admin=1.",
|
|
667
|
+
"impact": "Privilege escalation to admin role.",
|
|
668
|
+
"severity": "critical",
|
|
669
|
+
"confidence": "high",
|
|
670
|
+
"cwe": [
|
|
671
|
+
"CWE-269",
|
|
672
|
+
"CWE-285"
|
|
673
|
+
],
|
|
674
|
+
"owasp": [
|
|
675
|
+
"API5:2023"
|
|
676
|
+
],
|
|
677
|
+
"recommendation": "Remove client‑controlled role assignment and enforce server‑side role check.",
|
|
678
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
679
|
+
"status": "confirmed",
|
|
680
|
+
"verification": {
|
|
681
|
+
"verdict": "confirmed",
|
|
682
|
+
"reason_code": "traced",
|
|
683
|
+
"checked": [
|
|
684
|
+
"php-plain/admin/promote.php",
|
|
685
|
+
"inc/auth.php",
|
|
686
|
+
"inc/db.php"
|
|
687
|
+
],
|
|
688
|
+
"control_found": null,
|
|
689
|
+
"notes": "The handler updates the role based on a POST flag without verifying the caller’s role.",
|
|
690
|
+
"suggested_subtype": null,
|
|
691
|
+
"suggested_cwe": null
|
|
692
|
+
},
|
|
693
|
+
"consensus": {
|
|
694
|
+
"score": 0.95,
|
|
695
|
+
"agreement_level": "unanimous",
|
|
696
|
+
"voted_models": [
|
|
697
|
+
"gpt-oss:20b"
|
|
698
|
+
],
|
|
699
|
+
"rationale": "Client‑controlled role change."
|
|
700
|
+
},
|
|
701
|
+
"fingerprint": "kfp1:7bbf0150df170fbcf64d06f183e7d554ef32475d64081d2ddfd29fae23c3b7c1",
|
|
702
|
+
"baseline_state": "new"
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
"finding_id": "BFLA-0067-2",
|
|
706
|
+
"category": "bfla",
|
|
707
|
+
"subtype": "missing_role_check",
|
|
708
|
+
"title": "PHP admin promote user missing role check",
|
|
709
|
+
"route_id": "R-0067",
|
|
710
|
+
"endpoint": {
|
|
711
|
+
"method": "POST",
|
|
712
|
+
"path": "/php-plain/admin/promote.php"
|
|
713
|
+
},
|
|
714
|
+
"location": {
|
|
715
|
+
"file": "php-plain/admin/promote.php",
|
|
716
|
+
"start_line": 1,
|
|
717
|
+
"end_line": 8
|
|
718
|
+
},
|
|
719
|
+
"evidence": [
|
|
720
|
+
{
|
|
721
|
+
"file": "php-plain/admin/promote.php",
|
|
722
|
+
"line": 1,
|
|
723
|
+
"code": "<?php require_once '../inc/auth.php'; require_once '../inc/db.php';",
|
|
724
|
+
"why": "auth guard only checks session, no role"
|
|
725
|
+
}
|
|
726
|
+
],
|
|
727
|
+
"attack_scenario": "A logged‑in user can promote themselves to admin by posting is_admin=1.",
|
|
728
|
+
"impact": "Privilege escalation to admin role.",
|
|
729
|
+
"severity": "critical",
|
|
730
|
+
"confidence": "high",
|
|
731
|
+
"cwe": [
|
|
732
|
+
"CWE-285"
|
|
733
|
+
],
|
|
734
|
+
"owasp": [
|
|
735
|
+
"API5:2023"
|
|
736
|
+
],
|
|
737
|
+
"recommendation": "Add a server‑side role check before executing the role update.",
|
|
738
|
+
"fix_example": "if ($_SESSION['role'] !== 'admin') { http_response_code(403); exit; }",
|
|
739
|
+
"status": "confirmed",
|
|
740
|
+
"verification": {
|
|
741
|
+
"verdict": "confirmed",
|
|
742
|
+
"reason_code": "traced",
|
|
743
|
+
"checked": [
|
|
744
|
+
"php-plain/admin/promote.php",
|
|
745
|
+
"inc/auth.php",
|
|
746
|
+
"inc/db.php"
|
|
747
|
+
],
|
|
748
|
+
"control_found": null,
|
|
749
|
+
"notes": "Same as BFLA-0067-1; no role check before executing the UPDATE.",
|
|
750
|
+
"suggested_subtype": null,
|
|
751
|
+
"suggested_cwe": null
|
|
752
|
+
},
|
|
753
|
+
"consensus": {
|
|
754
|
+
"score": 0.95,
|
|
755
|
+
"agreement_level": "unanimous",
|
|
756
|
+
"voted_models": [
|
|
757
|
+
"gpt-oss:20b"
|
|
758
|
+
],
|
|
759
|
+
"rationale": "Missing role guard."
|
|
760
|
+
},
|
|
761
|
+
"fingerprint": "kfp1:fcc04941953145812ca686ad04ed8d05be443e0e7ce7d7cb326d9408fb45cd31",
|
|
762
|
+
"baseline_state": "new"
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
"finding_id": "INJ-I-0001-1",
|
|
766
|
+
"category": "sql_injection",
|
|
767
|
+
"subtype": "string_concatenation",
|
|
768
|
+
"title": "SQL Injection in classic-asp/shop/member.asp:6",
|
|
769
|
+
"candidate_id": "I-0001",
|
|
770
|
+
"location": {
|
|
771
|
+
"file": "classic-asp/shop/member.asp",
|
|
772
|
+
"start_line": 6
|
|
773
|
+
},
|
|
774
|
+
"evidence": [
|
|
775
|
+
{
|
|
776
|
+
"file": "classic-asp/shop/member.asp",
|
|
777
|
+
"start_line": 7,
|
|
778
|
+
"code": "sql = sql & \" WHERE member_id = \" & Request.Form(\"member_id\")",
|
|
779
|
+
"why": "Unsanitized input concatenated into query",
|
|
780
|
+
"line": 7
|
|
781
|
+
}
|
|
782
|
+
],
|
|
783
|
+
"severity": "critical",
|
|
784
|
+
"confidence": "high",
|
|
785
|
+
"cwe": [
|
|
786
|
+
"CWE-89"
|
|
787
|
+
],
|
|
788
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
789
|
+
"status": "confirmed",
|
|
790
|
+
"verification": {
|
|
791
|
+
"verdict": "confirmed",
|
|
792
|
+
"reason_code": "traced",
|
|
793
|
+
"checked": [
|
|
794
|
+
"classic-asp/shop/member.asp: lines 6-10",
|
|
795
|
+
"classic-asp/shop/member.asp: string concatenation of Request.Form(\"member_id\")"
|
|
796
|
+
],
|
|
797
|
+
"control_found": null,
|
|
798
|
+
"notes": "The code concatenates unsanitized form input directly into an SQL statement, enabling classic SQL injection.",
|
|
799
|
+
"suggested_subtype": null,
|
|
800
|
+
"suggested_cwe": null
|
|
801
|
+
},
|
|
802
|
+
"consensus": {
|
|
803
|
+
"score": 0.95,
|
|
804
|
+
"agreement_level": "unanimous",
|
|
805
|
+
"voted_models": [
|
|
806
|
+
"gpt-oss:20b"
|
|
807
|
+
],
|
|
808
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
809
|
+
},
|
|
810
|
+
"fingerprint": "kfp1:52c63ccaf583de3933acccb3eff1f79314c58ac311cdeecdfc26c798403a7c89",
|
|
811
|
+
"baseline_state": "new"
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
"finding_id": "INJ-I-0002-1",
|
|
815
|
+
"category": "sql_injection",
|
|
816
|
+
"subtype": "string_concatenation",
|
|
817
|
+
"title": "SQL Injection in classic-asp/shop/member.asp:7",
|
|
818
|
+
"candidate_id": "I-0002",
|
|
819
|
+
"location": {
|
|
820
|
+
"file": "classic-asp/shop/member.asp",
|
|
821
|
+
"start_line": 7
|
|
822
|
+
},
|
|
823
|
+
"evidence": [
|
|
824
|
+
{
|
|
825
|
+
"file": "classic-asp/shop/member.asp",
|
|
826
|
+
"start_line": 7,
|
|
827
|
+
"code": "sql = sql & \" WHERE member_id = \" & Request.Form(\"member_id\")",
|
|
828
|
+
"why": "Unsanitized input concatenated into query",
|
|
829
|
+
"line": 7
|
|
830
|
+
}
|
|
831
|
+
],
|
|
832
|
+
"severity": "critical",
|
|
833
|
+
"confidence": "high",
|
|
834
|
+
"cwe": [
|
|
835
|
+
"CWE-89"
|
|
836
|
+
],
|
|
837
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
838
|
+
"status": "confirmed",
|
|
839
|
+
"verification": {
|
|
840
|
+
"verdict": "confirmed",
|
|
841
|
+
"reason_code": "traced",
|
|
842
|
+
"checked": [
|
|
843
|
+
"classic-asp/shop/member.asp: lines 6-10",
|
|
844
|
+
"classic-asp/shop/member.asp: same string concatenation as INJ-I-0001-1"
|
|
845
|
+
],
|
|
846
|
+
"control_found": null,
|
|
847
|
+
"notes": "Duplicate evidence of unsanitized input concatenated into SQL; classic injection remains possible.",
|
|
848
|
+
"suggested_subtype": null,
|
|
849
|
+
"suggested_cwe": null
|
|
850
|
+
},
|
|
851
|
+
"consensus": {
|
|
852
|
+
"score": 0.95,
|
|
853
|
+
"agreement_level": "unanimous",
|
|
854
|
+
"voted_models": [
|
|
855
|
+
"gpt-oss:20b"
|
|
856
|
+
],
|
|
857
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
858
|
+
},
|
|
859
|
+
"fingerprint": "kfp1:a425457aee9a3c9db04caa44ddba7d35ecadd834bf5a0100207b9fce90ea85dd",
|
|
860
|
+
"baseline_state": "new"
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
"finding_id": "INJ-I-0003-1",
|
|
864
|
+
"category": "sql_injection",
|
|
865
|
+
"subtype": "string_concatenation",
|
|
866
|
+
"title": "SQL Injection in classic-asp/shop/member.asp:10",
|
|
867
|
+
"candidate_id": "I-0003",
|
|
868
|
+
"location": {
|
|
869
|
+
"file": "classic-asp/shop/member.asp",
|
|
870
|
+
"start_line": 10
|
|
871
|
+
},
|
|
872
|
+
"evidence": [
|
|
873
|
+
{
|
|
874
|
+
"file": "classic-asp/shop/member.asp",
|
|
875
|
+
"start_line": 10,
|
|
876
|
+
"code": "conn.Execute \"DELETE FROM members WHERE member_id = \" & Request.QueryString(\"member_id\")",
|
|
877
|
+
"why": "Unsanitized input concatenated into query",
|
|
878
|
+
"line": 10
|
|
879
|
+
}
|
|
880
|
+
],
|
|
881
|
+
"severity": "critical",
|
|
882
|
+
"confidence": "high",
|
|
883
|
+
"cwe": [
|
|
884
|
+
"CWE-89"
|
|
885
|
+
],
|
|
886
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
887
|
+
"status": "confirmed",
|
|
888
|
+
"verification": {
|
|
889
|
+
"verdict": "confirmed",
|
|
890
|
+
"reason_code": "traced",
|
|
891
|
+
"checked": [
|
|
892
|
+
"classic-asp/shop/member.asp line 10",
|
|
893
|
+
"global include files for request handling",
|
|
894
|
+
"absence of integer casting or allow‑list for member_id"
|
|
895
|
+
],
|
|
896
|
+
"control_found": null,
|
|
897
|
+
"notes": "The code concatenates the raw QueryString value into a DELETE statement with no sanitisation or type enforcement. No global filter or helper casts the value to an integer.",
|
|
898
|
+
"suggested_subtype": null,
|
|
899
|
+
"suggested_cwe": null
|
|
900
|
+
},
|
|
901
|
+
"consensus": {
|
|
902
|
+
"score": 0.95,
|
|
903
|
+
"agreement_level": "unanimous",
|
|
904
|
+
"voted_models": [
|
|
905
|
+
"gpt-oss:20b"
|
|
906
|
+
],
|
|
907
|
+
"rationale": "Direct string concatenation of an unvalidated request parameter into a SQL statement."
|
|
908
|
+
},
|
|
909
|
+
"fingerprint": "kfp1:f1fb34f9c39374bc339b3dc230f5ac6b00731c69bf6cd1399b983ba2d2833525",
|
|
910
|
+
"baseline_state": "new"
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
"finding_id": "INJ-I-0004-1",
|
|
914
|
+
"category": "sql_injection",
|
|
915
|
+
"subtype": "string_concatenation",
|
|
916
|
+
"title": "SQL Injection in classic-asp/shop/order.asp:6",
|
|
917
|
+
"candidate_id": "I-0004",
|
|
918
|
+
"location": {
|
|
919
|
+
"file": "classic-asp/shop/order.asp",
|
|
920
|
+
"start_line": 6
|
|
921
|
+
},
|
|
922
|
+
"evidence": [
|
|
923
|
+
{
|
|
924
|
+
"file": "classic-asp/shop/order.asp",
|
|
925
|
+
"start_line": 6,
|
|
926
|
+
"code": "Set rs = conn.Execute(\"SELECT * FROM orders WHERE id = \" & id)",
|
|
927
|
+
"why": "Unsanitized input concatenated into query",
|
|
928
|
+
"line": 6
|
|
929
|
+
}
|
|
930
|
+
],
|
|
931
|
+
"severity": "critical",
|
|
932
|
+
"confidence": "high",
|
|
933
|
+
"cwe": [
|
|
934
|
+
"CWE-89"
|
|
935
|
+
],
|
|
936
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
937
|
+
"status": "confirmed",
|
|
938
|
+
"verification": {
|
|
939
|
+
"verdict": "confirmed",
|
|
940
|
+
"reason_code": "traced",
|
|
941
|
+
"checked": [
|
|
942
|
+
"classic-asp/shop/order.asp line 6",
|
|
943
|
+
"source of id variable (Request.QueryString or Request.Form)",
|
|
944
|
+
"no integer cast or allow‑list applied"
|
|
945
|
+
],
|
|
946
|
+
"control_found": null,
|
|
947
|
+
"notes": "The variable `id` is derived from user input and concatenated directly into a SELECT statement. No sanitisation or type enforcement is present.",
|
|
948
|
+
"suggested_subtype": null,
|
|
949
|
+
"suggested_cwe": null
|
|
950
|
+
},
|
|
951
|
+
"consensus": {
|
|
952
|
+
"score": 0.95,
|
|
953
|
+
"agreement_level": "unanimous",
|
|
954
|
+
"voted_models": [
|
|
955
|
+
"gpt-oss:20b"
|
|
956
|
+
],
|
|
957
|
+
"rationale": "Unfiltered user input is used in a SQL query."
|
|
958
|
+
},
|
|
959
|
+
"fingerprint": "kfp1:07b1c914202a928e4156e6b6e9e5ab32fe7a75cdceda182d55d68051eeb61ac9",
|
|
960
|
+
"baseline_state": "new"
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
"finding_id": "INJ-I-0009-1",
|
|
964
|
+
"category": "sql_injection",
|
|
965
|
+
"subtype": "string_concatenation",
|
|
966
|
+
"title": "SQL Injection in dotnet-legacy/Web/Orders/EditOrder.aspx.vb:7",
|
|
967
|
+
"candidate_id": "I-0009",
|
|
968
|
+
"location": {
|
|
969
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
970
|
+
"start_line": 7
|
|
971
|
+
},
|
|
972
|
+
"evidence": [
|
|
973
|
+
{
|
|
974
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
975
|
+
"start_line": 7,
|
|
976
|
+
"code": "Dim sql As String = \"UPDATE Orders SET Note = '\" & txtNote.Text & \"'\"",
|
|
977
|
+
"why": "Unsanitized input concatenated into query",
|
|
978
|
+
"line": 7
|
|
979
|
+
}
|
|
980
|
+
],
|
|
981
|
+
"severity": "critical",
|
|
982
|
+
"confidence": "high",
|
|
983
|
+
"cwe": [
|
|
984
|
+
"CWE-89"
|
|
985
|
+
],
|
|
986
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
987
|
+
"status": "confirmed",
|
|
988
|
+
"verification": {
|
|
989
|
+
"verdict": "confirmed",
|
|
990
|
+
"reason_code": "traced",
|
|
991
|
+
"checked": [
|
|
992
|
+
"dotnet-legacy/Web/Orders/EditOrder.aspx.vb line 7",
|
|
993
|
+
"usage of txtNote.Text in SQL string",
|
|
994
|
+
"no parameterisation or sanitisation"
|
|
995
|
+
],
|
|
996
|
+
"control_found": null,
|
|
997
|
+
"notes": "The Note field is concatenated directly into an UPDATE statement. No server‑side validation or parameter binding is applied.",
|
|
998
|
+
"suggested_subtype": null,
|
|
999
|
+
"suggested_cwe": null
|
|
1000
|
+
},
|
|
1001
|
+
"consensus": {
|
|
1002
|
+
"score": 0.95,
|
|
1003
|
+
"agreement_level": "unanimous",
|
|
1004
|
+
"voted_models": [
|
|
1005
|
+
"gpt-oss:20b"
|
|
1006
|
+
],
|
|
1007
|
+
"rationale": "Unescaped user input is used in a SQL command."
|
|
1008
|
+
},
|
|
1009
|
+
"fingerprint": "kfp1:999e755f32c1edc6b46f7497ebdfd065d8afcc3518cab3211f45fc11a7342afd",
|
|
1010
|
+
"baseline_state": "new"
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
"finding_id": "INJ-I-0010-1",
|
|
1014
|
+
"category": "sql_injection",
|
|
1015
|
+
"subtype": "string_concatenation",
|
|
1016
|
+
"title": "SQL Injection in dotnet-legacy/Web/Orders/EditOrder.aspx.vb:8",
|
|
1017
|
+
"candidate_id": "I-0010",
|
|
1018
|
+
"location": {
|
|
1019
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
1020
|
+
"start_line": 8
|
|
1021
|
+
},
|
|
1022
|
+
"evidence": [
|
|
1023
|
+
{
|
|
1024
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
1025
|
+
"start_line": 8,
|
|
1026
|
+
"code": "sql &= \" WHERE OrderId = \" & hfOrderId.Value",
|
|
1027
|
+
"why": "Unsanitized input concatenated into query",
|
|
1028
|
+
"line": 8
|
|
1029
|
+
}
|
|
1030
|
+
],
|
|
1031
|
+
"severity": "critical",
|
|
1032
|
+
"confidence": "high",
|
|
1033
|
+
"cwe": [
|
|
1034
|
+
"CWE-89"
|
|
1035
|
+
],
|
|
1036
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
1037
|
+
"status": "confirmed",
|
|
1038
|
+
"verification": {
|
|
1039
|
+
"verdict": "confirmed",
|
|
1040
|
+
"reason_code": "traced",
|
|
1041
|
+
"checked": [
|
|
1042
|
+
"dotnet-legacy/Web/Orders/EditOrder.aspx.vb line 8",
|
|
1043
|
+
"usage of hfOrderId.Value in SQL string",
|
|
1044
|
+
"no validation or parameterisation"
|
|
1045
|
+
],
|
|
1046
|
+
"control_found": null,
|
|
1047
|
+
"notes": "The hidden field value is concatenated into the WHERE clause without any type enforcement or parameter binding.",
|
|
1048
|
+
"suggested_subtype": null,
|
|
1049
|
+
"suggested_cwe": null
|
|
1050
|
+
},
|
|
1051
|
+
"consensus": {
|
|
1052
|
+
"score": 0.95,
|
|
1053
|
+
"agreement_level": "unanimous",
|
|
1054
|
+
"voted_models": [
|
|
1055
|
+
"gpt-oss:20b"
|
|
1056
|
+
],
|
|
1057
|
+
"rationale": "Direct string concatenation of a potentially tampered hidden field."
|
|
1058
|
+
},
|
|
1059
|
+
"fingerprint": "kfp1:323eb8697c45d7b7466292d80a0763dd2310608e0975d837273bde14e3458f59",
|
|
1060
|
+
"baseline_state": "new"
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
"finding_id": "INJ-I-0011-1",
|
|
1064
|
+
"category": "sql_injection",
|
|
1065
|
+
"subtype": "string_concatenation",
|
|
1066
|
+
"title": "SQL Injection in dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:13",
|
|
1067
|
+
"candidate_id": "I-0011",
|
|
1068
|
+
"location": {
|
|
1069
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
1070
|
+
"start_line": 13
|
|
1071
|
+
},
|
|
1072
|
+
"evidence": [
|
|
1073
|
+
{
|
|
1074
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
1075
|
+
"start_line": 13,
|
|
1076
|
+
"code": "var cmd = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = \" + id, conn);",
|
|
1077
|
+
"why": "Unsanitized input concatenated into query",
|
|
1078
|
+
"line": 13
|
|
1079
|
+
}
|
|
1080
|
+
],
|
|
1081
|
+
"severity": "critical",
|
|
1082
|
+
"confidence": "high",
|
|
1083
|
+
"cwe": [
|
|
1084
|
+
"CWE-89"
|
|
1085
|
+
],
|
|
1086
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
1087
|
+
"status": "confirmed",
|
|
1088
|
+
"verification": {
|
|
1089
|
+
"verdict": "confirmed",
|
|
1090
|
+
"reason_code": "traced",
|
|
1091
|
+
"checked": [
|
|
1092
|
+
"dotnet-legacy/Web/Orders/OrderDetail.aspx.cs line 13",
|
|
1093
|
+
"string concatenation of id into SELECT statement",
|
|
1094
|
+
"no parameterisation"
|
|
1095
|
+
],
|
|
1096
|
+
"control_found": null,
|
|
1097
|
+
"notes": "The `id` value is concatenated directly into the SQL command. No server‑side validation or parameter binding is present.",
|
|
1098
|
+
"suggested_subtype": null,
|
|
1099
|
+
"suggested_cwe": null
|
|
1100
|
+
},
|
|
1101
|
+
"consensus": {
|
|
1102
|
+
"score": 0.95,
|
|
1103
|
+
"agreement_level": "unanimous",
|
|
1104
|
+
"voted_models": [
|
|
1105
|
+
"gpt-oss:20b"
|
|
1106
|
+
],
|
|
1107
|
+
"rationale": "Unescaped user input used in a SQL query."
|
|
1108
|
+
},
|
|
1109
|
+
"fingerprint": "kfp1:c9aa426f72d032a9b0729b0905c9c25a6fb8844a92a91c45d72d3e26170bbae5",
|
|
1110
|
+
"baseline_state": "new"
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
"finding_id": "INJ-I-0011-2",
|
|
1114
|
+
"category": "sql_injection",
|
|
1115
|
+
"subtype": "string_concatenation",
|
|
1116
|
+
"title": "SQL Injection in dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:19",
|
|
1117
|
+
"candidate_id": "I-0011",
|
|
1118
|
+
"location": {
|
|
1119
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
1120
|
+
"start_line": 19
|
|
1121
|
+
},
|
|
1122
|
+
"evidence": [
|
|
1123
|
+
{
|
|
1124
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
1125
|
+
"start_line": 19,
|
|
1126
|
+
"code": "string sql2 = string.Format(\"SELECT * FROM Logs WHERE Ref = '{0}'\", Request[\"ref\"]);",
|
|
1127
|
+
"why": "Unsanitized input concatenated into query",
|
|
1128
|
+
"line": 19
|
|
1129
|
+
}
|
|
1130
|
+
],
|
|
1131
|
+
"severity": "critical",
|
|
1132
|
+
"confidence": "high",
|
|
1133
|
+
"cwe": [
|
|
1134
|
+
"CWE-89"
|
|
1135
|
+
],
|
|
1136
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
1137
|
+
"status": "confirmed",
|
|
1138
|
+
"verification": {
|
|
1139
|
+
"verdict": "confirmed",
|
|
1140
|
+
"reason_code": "traced",
|
|
1141
|
+
"checked": [
|
|
1142
|
+
"dotnet-legacy/Web/Orders/OrderDetail.aspx.cs line 19",
|
|
1143
|
+
"string.Format usage with Request[\"ref\"]",
|
|
1144
|
+
"no sanitisation or parameterisation"
|
|
1145
|
+
],
|
|
1146
|
+
"control_found": null,
|
|
1147
|
+
"notes": "The `ref` request parameter is interpolated into a SELECT statement via string.Format, exposing the query to injection.",
|
|
1148
|
+
"suggested_subtype": null,
|
|
1149
|
+
"suggested_cwe": null
|
|
1150
|
+
},
|
|
1151
|
+
"consensus": {
|
|
1152
|
+
"score": 0.95,
|
|
1153
|
+
"agreement_level": "unanimous",
|
|
1154
|
+
"voted_models": [
|
|
1155
|
+
"gpt-oss:20b"
|
|
1156
|
+
],
|
|
1157
|
+
"rationale": "Unescaped request data in SQL."
|
|
1158
|
+
},
|
|
1159
|
+
"fingerprint": "kfp1:c0adff113329292a51bb7d2f2aa03dd242c6d767cee060a789ff750f3ab9b946",
|
|
1160
|
+
"baseline_state": "new"
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
"finding_id": "INJ-I-0011-3",
|
|
1164
|
+
"category": "sql_injection",
|
|
1165
|
+
"subtype": "string_interpolation",
|
|
1166
|
+
"title": "SQL Injection in dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:20",
|
|
1167
|
+
"candidate_id": "I-0011",
|
|
1168
|
+
"location": {
|
|
1169
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
1170
|
+
"start_line": 20
|
|
1171
|
+
},
|
|
1172
|
+
"evidence": [
|
|
1173
|
+
{
|
|
1174
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
1175
|
+
"start_line": 20,
|
|
1176
|
+
"code": "string sql3 = $\"DELETE FROM Carts WHERE CartId = {Request.Form[\"cart\"]}\";",
|
|
1177
|
+
"why": "Unsanitized input concatenated into query",
|
|
1178
|
+
"line": 20
|
|
1179
|
+
}
|
|
1180
|
+
],
|
|
1181
|
+
"severity": "critical",
|
|
1182
|
+
"confidence": "high",
|
|
1183
|
+
"cwe": [
|
|
1184
|
+
"CWE-89"
|
|
1185
|
+
],
|
|
1186
|
+
"recommendation": "Use parameterized queries to avoid injection",
|
|
1187
|
+
"status": "confirmed",
|
|
1188
|
+
"verification": {
|
|
1189
|
+
"verdict": "confirmed",
|
|
1190
|
+
"reason_code": "traced",
|
|
1191
|
+
"checked": [
|
|
1192
|
+
"dotnet-legacy/Web/Orders/OrderDetail.aspx.cs line 20",
|
|
1193
|
+
"string interpolation of Request.Form[\"cart\"] into DELETE statement",
|
|
1194
|
+
"no parameterisation"
|
|
1195
|
+
],
|
|
1196
|
+
"control_found": null,
|
|
1197
|
+
"notes": "The cart ID from form data is directly interpolated into a DELETE query, allowing injection.",
|
|
1198
|
+
"suggested_subtype": null,
|
|
1199
|
+
"suggested_cwe": null
|
|
1200
|
+
},
|
|
1201
|
+
"consensus": {
|
|
1202
|
+
"score": 0.95,
|
|
1203
|
+
"agreement_level": "unanimous",
|
|
1204
|
+
"voted_models": [
|
|
1205
|
+
"gpt-oss:20b"
|
|
1206
|
+
],
|
|
1207
|
+
"rationale": "Direct string interpolation of user input into SQL."
|
|
1208
|
+
},
|
|
1209
|
+
"fingerprint": "kfp1:85f16690de7a315c7ef9dc35f534e32d9f39803093f42a6ec4200391523c52c2",
|
|
1210
|
+
"baseline_state": "new"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
"finding_id": "INJ-I-0014-1",
|
|
1214
|
+
"category": "sql_injection",
|
|
1215
|
+
"subtype": "string_concatenation",
|
|
1216
|
+
"title": "SQL Injection in java-legacy/WebContent/order/detail.jsp:5",
|
|
1217
|
+
"candidate_id": "I-0014",
|
|
1218
|
+
"location": {
|
|
1219
|
+
"file": "java-legacy/WebContent/order/detail.jsp",
|
|
1220
|
+
"start_line": 5
|
|
1221
|
+
},
|
|
1222
|
+
"evidence": [
|
|
1223
|
+
{
|
|
1224
|
+
"file": "java-legacy/WebContent/order/detail.jsp",
|
|
1225
|
+
"start_line": 5,
|
|
1226
|
+
"code": "ResultSet rs = c.createStatement().executeQuery(\"SELECT * FROM orders WHERE order_no = '\" + no + \"'\");",
|
|
1227
|
+
"why": "Unsanitized input concatenated into query",
|
|
1228
|
+
"line": 5
|
|
1229
|
+
}
|
|
1230
|
+
],
|
|
1231
|
+
"severity": "critical",
|
|
1232
|
+
"confidence": "high",
|
|
1233
|
+
"cwe": [
|
|
1234
|
+
"CWE-89"
|
|
1235
|
+
],
|
|
1236
|
+
"recommendation": "Use PreparedStatement with parameters",
|
|
1237
|
+
"status": "confirmed",
|
|
1238
|
+
"verification": {
|
|
1239
|
+
"verdict": "confirmed",
|
|
1240
|
+
"reason_code": "traced",
|
|
1241
|
+
"checked": [
|
|
1242
|
+
"java-legacy/WebContent/order/detail.jsp line 5",
|
|
1243
|
+
"global filters in java-legacy/WEB-INF/web.xml",
|
|
1244
|
+
"any utility classes in java-legacy/src/com/acme/utils/StringUtils.java"
|
|
1245
|
+
],
|
|
1246
|
+
"control_found": null,
|
|
1247
|
+
"notes": "The JSP concatenates the request parameter \"no\" directly into the SQL string. No integer cast, parameter binding, or allow‑list is applied, making the query susceptible to injection.",
|
|
1248
|
+
"suggested_subtype": null,
|
|
1249
|
+
"suggested_cwe": null
|
|
1250
|
+
},
|
|
1251
|
+
"consensus": {
|
|
1252
|
+
"score": 0.95,
|
|
1253
|
+
"agreement_level": "unanimous",
|
|
1254
|
+
"voted_models": [
|
|
1255
|
+
"gpt-oss:20b"
|
|
1256
|
+
],
|
|
1257
|
+
"rationale": "Unsanitized string concatenation with no mitigating control."
|
|
1258
|
+
},
|
|
1259
|
+
"fingerprint": "kfp1:b8e7acb1ca7119156e96cf2bd42356b53bb6107a0a904b492534714730ac45f8",
|
|
1260
|
+
"baseline_state": "new"
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
"finding_id": "INJ-I-0016-1",
|
|
1264
|
+
"category": "sql_injection",
|
|
1265
|
+
"subtype": "string_concatenation",
|
|
1266
|
+
"title": "SQL Injection in java-legacy/src/com/acme/struts/EditUserAction.java:13",
|
|
1267
|
+
"candidate_id": "I-0016",
|
|
1268
|
+
"location": {
|
|
1269
|
+
"file": "java-legacy/src/com/acme/struts/EditUserAction.java",
|
|
1270
|
+
"start_line": 13
|
|
1271
|
+
},
|
|
1272
|
+
"evidence": [
|
|
1273
|
+
{
|
|
1274
|
+
"file": "java-legacy/src/com/acme/struts/EditUserAction.java",
|
|
1275
|
+
"start_line": 13,
|
|
1276
|
+
"code": "PreparedStatement ps = conn.prepareStatement(\"UPDATE users SET email = '\" + email + \"' WHERE id = \" + userId);",
|
|
1277
|
+
"why": "Unsanitized input concatenated into query",
|
|
1278
|
+
"line": 13
|
|
1279
|
+
}
|
|
1280
|
+
],
|
|
1281
|
+
"severity": "critical",
|
|
1282
|
+
"confidence": "high",
|
|
1283
|
+
"cwe": [
|
|
1284
|
+
"CWE-89"
|
|
1285
|
+
],
|
|
1286
|
+
"recommendation": "Use parameterized queries",
|
|
1287
|
+
"status": "confirmed",
|
|
1288
|
+
"verification": {
|
|
1289
|
+
"verdict": "confirmed",
|
|
1290
|
+
"reason_code": "traced",
|
|
1291
|
+
"checked": [
|
|
1292
|
+
"java-legacy/src/com/acme/struts/EditUserAction.java line 13",
|
|
1293
|
+
"global filters in java-legacy/src/com/acme/filters/RequestFilter.java",
|
|
1294
|
+
"any helper methods in java-legacy/src/com/acme/utils/SqlSanitizer.java"
|
|
1295
|
+
],
|
|
1296
|
+
"control_found": null,
|
|
1297
|
+
"notes": "The code builds the UPDATE statement by concatenating the email and userId values directly into the SQL string, even though a PreparedStatement object is used. No parameter placeholders or sanitization are present, so the statement is vulnerable.",
|
|
1298
|
+
"suggested_subtype": null,
|
|
1299
|
+
"suggested_cwe": null
|
|
1300
|
+
},
|
|
1301
|
+
"consensus": {
|
|
1302
|
+
"score": 0.95,
|
|
1303
|
+
"agreement_level": "unanimous",
|
|
1304
|
+
"voted_models": [
|
|
1305
|
+
"gpt-oss:20b"
|
|
1306
|
+
],
|
|
1307
|
+
"rationale": "String concatenation inside a PreparedStatement defeats its purpose."
|
|
1308
|
+
},
|
|
1309
|
+
"fingerprint": "kfp1:2976fb21c23c927d1de0f3af7dc883d9b66a1d051ec67bb9ec1b44396f6479fc",
|
|
1310
|
+
"baseline_state": "new"
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
"finding_id": "INJ-I-0017-1",
|
|
1314
|
+
"category": "sql_injection",
|
|
1315
|
+
"subtype": "string_concatenation",
|
|
1316
|
+
"title": "SQL Injection in java-legacy/src/com/acme/web/InvoiceController.java:29",
|
|
1317
|
+
"candidate_id": "I-0017",
|
|
1318
|
+
"location": {
|
|
1319
|
+
"file": "java-legacy/src/com/acme/web/InvoiceController.java",
|
|
1320
|
+
"start_line": 29
|
|
1321
|
+
},
|
|
1322
|
+
"evidence": [
|
|
1323
|
+
{
|
|
1324
|
+
"file": "java-legacy/src/com/acme/web/InvoiceController.java",
|
|
1325
|
+
"start_line": 29,
|
|
1326
|
+
"code": "model.addAttribute(\"list\", session.createQuery(\"from Invoice i where i.customer = '\" + customer + \"'\").list());",
|
|
1327
|
+
"why": "Unsanitized input concatenated into query",
|
|
1328
|
+
"line": 29
|
|
1329
|
+
}
|
|
1330
|
+
],
|
|
1331
|
+
"severity": "critical",
|
|
1332
|
+
"confidence": "high",
|
|
1333
|
+
"cwe": [
|
|
1334
|
+
"CWE-89"
|
|
1335
|
+
],
|
|
1336
|
+
"recommendation": "Use parameterized HQL/JPQL",
|
|
1337
|
+
"status": "confirmed",
|
|
1338
|
+
"verification": {
|
|
1339
|
+
"verdict": "confirmed",
|
|
1340
|
+
"reason_code": "traced",
|
|
1341
|
+
"checked": [
|
|
1342
|
+
"java-legacy/src/com/acme/web/InvoiceController.java line 29",
|
|
1343
|
+
"global filters in java-legacy/src/com/acme/security/AuthorizationFilter.java",
|
|
1344
|
+
"any helper methods in java-legacy/src/com/acme/utils/HibernateUtil.java"
|
|
1345
|
+
],
|
|
1346
|
+
"control_found": null,
|
|
1347
|
+
"notes": "The HQL query is constructed by concatenating the customer value directly into the string. No parameter binding or allow‑list is used, making the query vulnerable to injection.",
|
|
1348
|
+
"suggested_subtype": null,
|
|
1349
|
+
"suggested_cwe": null
|
|
1350
|
+
},
|
|
1351
|
+
"consensus": {
|
|
1352
|
+
"score": 0.95,
|
|
1353
|
+
"agreement_level": "unanimous",
|
|
1354
|
+
"voted_models": [
|
|
1355
|
+
"gpt-oss:20b"
|
|
1356
|
+
],
|
|
1357
|
+
"rationale": "Direct string interpolation without sanitization."
|
|
1358
|
+
},
|
|
1359
|
+
"fingerprint": "kfp1:7796f734194bfe71dc7268f597efd6338a4e967a788e0220edb61b8e1c198b16",
|
|
1360
|
+
"baseline_state": "new"
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
"finding_id": "INJ-I-0020-1",
|
|
1364
|
+
"category": "sql_injection",
|
|
1365
|
+
"subtype": "string_concatenation",
|
|
1366
|
+
"title": "SQL Injection in node-express/routes/orders.js:17",
|
|
1367
|
+
"candidate_id": "I-0020",
|
|
1368
|
+
"location": {
|
|
1369
|
+
"file": "node-express/routes/orders.js",
|
|
1370
|
+
"start_line": 17
|
|
1371
|
+
},
|
|
1372
|
+
"evidence": [
|
|
1373
|
+
{
|
|
1374
|
+
"file": "node-express/routes/orders.js",
|
|
1375
|
+
"start_line": 17,
|
|
1376
|
+
"code": "db.query(\"SELECT * FROM reports WHERE month = '\" + req.query.month + \"'\")",
|
|
1377
|
+
"why": "Unsanitized input concatenated into query",
|
|
1378
|
+
"line": 17
|
|
1379
|
+
}
|
|
1380
|
+
],
|
|
1381
|
+
"severity": "critical",
|
|
1382
|
+
"confidence": "high",
|
|
1383
|
+
"cwe": [
|
|
1384
|
+
"CWE-89"
|
|
1385
|
+
],
|
|
1386
|
+
"recommendation": "Use parameterized queries",
|
|
1387
|
+
"status": "confirmed",
|
|
1388
|
+
"verification": {
|
|
1389
|
+
"verdict": "confirmed",
|
|
1390
|
+
"reason_code": "traced",
|
|
1391
|
+
"checked": [
|
|
1392
|
+
"node-express/routes/orders.js line 17",
|
|
1393
|
+
"global middleware in node-express/app.js",
|
|
1394
|
+
"any sanitization utilities in node-express/lib/sanitize.js"
|
|
1395
|
+
],
|
|
1396
|
+
"control_found": null,
|
|
1397
|
+
"notes": "The query concatenates the month parameter directly into the SQL string. No type casting, parameter binding, or validation is performed, so the endpoint is vulnerable.",
|
|
1398
|
+
"suggested_subtype": null,
|
|
1399
|
+
"suggested_cwe": null
|
|
1400
|
+
},
|
|
1401
|
+
"consensus": {
|
|
1402
|
+
"score": 0.95,
|
|
1403
|
+
"agreement_level": "unanimous",
|
|
1404
|
+
"voted_models": [
|
|
1405
|
+
"gpt-oss:20b"
|
|
1406
|
+
],
|
|
1407
|
+
"rationale": "Unsanitized concatenation of user input."
|
|
1408
|
+
},
|
|
1409
|
+
"fingerprint": "kfp1:fc0f31011ca4510d026845f7fb0dcd77812ef24f6ea5329ad24fbc08a15b58ff",
|
|
1410
|
+
"baseline_state": "new"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"finding_id": "INJ-I-0020-2",
|
|
1414
|
+
"category": "nosql_injection",
|
|
1415
|
+
"subtype": "operator_injection",
|
|
1416
|
+
"title": "NoSQL Injection in node-express/routes/orders.js:25",
|
|
1417
|
+
"candidate_id": "I-0020",
|
|
1418
|
+
"location": {
|
|
1419
|
+
"file": "node-express/routes/orders.js",
|
|
1420
|
+
"start_line": 25
|
|
1421
|
+
},
|
|
1422
|
+
"evidence": [
|
|
1423
|
+
{
|
|
1424
|
+
"file": "node-express/routes/orders.js",
|
|
1425
|
+
"start_line": 25,
|
|
1426
|
+
"code": "db.collection('items').find({ $where: \"this.name == '\" + req.query.n + \"'\" })",
|
|
1427
|
+
"why": "Unsanitized input used in $where string",
|
|
1428
|
+
"line": 25
|
|
1429
|
+
}
|
|
1430
|
+
],
|
|
1431
|
+
"severity": "critical",
|
|
1432
|
+
"confidence": "high",
|
|
1433
|
+
"cwe": [
|
|
1434
|
+
"CWE-89"
|
|
1435
|
+
],
|
|
1436
|
+
"recommendation": "Avoid $where; use parameterized queries or safe filters",
|
|
1437
|
+
"status": "confirmed",
|
|
1438
|
+
"verification": {
|
|
1439
|
+
"verdict": "confirmed",
|
|
1440
|
+
"reason_code": "traced",
|
|
1441
|
+
"checked": [
|
|
1442
|
+
"node-express/routes/orders.js line 25",
|
|
1443
|
+
"global middleware in node-express/app.js",
|
|
1444
|
+
"any sanitization utilities in node-express/lib/sanitize.js"
|
|
1445
|
+
],
|
|
1446
|
+
"control_found": null,
|
|
1447
|
+
"notes": "The $where string is built by concatenating the request query directly, exposing the collection to NoSQL injection. No validation or allow‑list is applied.",
|
|
1448
|
+
"suggested_subtype": null,
|
|
1449
|
+
"suggested_cwe": null
|
|
1450
|
+
},
|
|
1451
|
+
"consensus": {
|
|
1452
|
+
"score": 0.95,
|
|
1453
|
+
"agreement_level": "unanimous",
|
|
1454
|
+
"voted_models": [
|
|
1455
|
+
"gpt-oss:20b"
|
|
1456
|
+
],
|
|
1457
|
+
"rationale": "Direct interpolation into $where clause."
|
|
1458
|
+
},
|
|
1459
|
+
"fingerprint": "kfp1:3c8358a689974ac6ce80d5d8933718bcf7ce7c7afafe2196d8fcfb9abcde049b",
|
|
1460
|
+
"baseline_state": "new"
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
"finding_id": "INJ-I-0020-3",
|
|
1464
|
+
"category": "sql_injection",
|
|
1465
|
+
"subtype": "string_interpolation",
|
|
1466
|
+
"title": "SQL Injection in node-express/routes/orders.js:29",
|
|
1467
|
+
"candidate_id": "I-0020",
|
|
1468
|
+
"location": {
|
|
1469
|
+
"file": "node-express/routes/orders.js",
|
|
1470
|
+
"start_line": 29
|
|
1471
|
+
},
|
|
1472
|
+
"evidence": [
|
|
1473
|
+
{
|
|
1474
|
+
"file": "node-express/routes/orders.js",
|
|
1475
|
+
"start_line": 29,
|
|
1476
|
+
"code": "db.query(`SELECT COUNT(*) AS n FROM orders WHERE shop_id = ${req.query.shop}`)",
|
|
1477
|
+
"why": "Unsanitized input interpolated into query",
|
|
1478
|
+
"line": 29
|
|
1479
|
+
}
|
|
1480
|
+
],
|
|
1481
|
+
"severity": "critical",
|
|
1482
|
+
"confidence": "high",
|
|
1483
|
+
"cwe": [
|
|
1484
|
+
"CWE-89"
|
|
1485
|
+
],
|
|
1486
|
+
"recommendation": "Use parameterized queries",
|
|
1487
|
+
"status": "confirmed",
|
|
1488
|
+
"verification": {
|
|
1489
|
+
"verdict": "confirmed",
|
|
1490
|
+
"reason_code": "traced",
|
|
1491
|
+
"checked": [
|
|
1492
|
+
"node-express/routes/orders.js line 29",
|
|
1493
|
+
"global middleware in node-express/app.js",
|
|
1494
|
+
"any sanitization utilities in node-express/lib/sanitize.js"
|
|
1495
|
+
],
|
|
1496
|
+
"control_found": null,
|
|
1497
|
+
"notes": "The shop_id value is interpolated directly into the SQL string. No integer cast or parameter binding is present, making the query vulnerable.",
|
|
1498
|
+
"suggested_subtype": null,
|
|
1499
|
+
"suggested_cwe": null
|
|
1500
|
+
},
|
|
1501
|
+
"consensus": {
|
|
1502
|
+
"score": 0.95,
|
|
1503
|
+
"agreement_level": "unanimous",
|
|
1504
|
+
"voted_models": [
|
|
1505
|
+
"gpt-oss:20b"
|
|
1506
|
+
],
|
|
1507
|
+
"rationale": "Unsanitized interpolation."
|
|
1508
|
+
},
|
|
1509
|
+
"fingerprint": "kfp1:2abbfba5afbab6fe47146424b128ea7bf5496a9ec3ae59589a94d2eccd4c3d66",
|
|
1510
|
+
"baseline_state": "new"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
"finding_id": "INJ-I-0020-4",
|
|
1514
|
+
"category": "nosql_injection",
|
|
1515
|
+
"subtype": "operator_injection",
|
|
1516
|
+
"title": "NoSQL Injection in node-express/routes/orders.js:21",
|
|
1517
|
+
"candidate_id": "I-0020",
|
|
1518
|
+
"location": {
|
|
1519
|
+
"file": "node-express/routes/orders.js",
|
|
1520
|
+
"start_line": 21
|
|
1521
|
+
},
|
|
1522
|
+
"evidence": [
|
|
1523
|
+
{
|
|
1524
|
+
"file": "node-express/routes/orders.js",
|
|
1525
|
+
"start_line": 21,
|
|
1526
|
+
"code": "db.collection('users').findOne(req.body)",
|
|
1527
|
+
"why": "Unsanitized request body used directly as filter",
|
|
1528
|
+
"line": 21
|
|
1529
|
+
}
|
|
1530
|
+
],
|
|
1531
|
+
"severity": "critical",
|
|
1532
|
+
"confidence": "high",
|
|
1533
|
+
"cwe": [
|
|
1534
|
+
"CWE-89"
|
|
1535
|
+
],
|
|
1536
|
+
"recommendation": "Validate and sanitize request body before using as query",
|
|
1537
|
+
"status": "confirmed",
|
|
1538
|
+
"verification": {
|
|
1539
|
+
"verdict": "confirmed",
|
|
1540
|
+
"reason_code": "traced",
|
|
1541
|
+
"checked": [
|
|
1542
|
+
"node-express/routes/orders.js line 21",
|
|
1543
|
+
"global middleware in node-express/app.js",
|
|
1544
|
+
"any sanitization utilities in node-express/lib/sanitize.js"
|
|
1545
|
+
],
|
|
1546
|
+
"control_found": null,
|
|
1547
|
+
"notes": "The request body is passed directly to findOne without validation or sanitization, exposing the collection to NoSQL injection.",
|
|
1548
|
+
"suggested_subtype": null,
|
|
1549
|
+
"suggested_cwe": null
|
|
1550
|
+
},
|
|
1551
|
+
"consensus": {
|
|
1552
|
+
"score": 0.95,
|
|
1553
|
+
"agreement_level": "unanimous",
|
|
1554
|
+
"voted_models": [
|
|
1555
|
+
"gpt-oss:20b"
|
|
1556
|
+
],
|
|
1557
|
+
"rationale": "Direct use of unsanitized request body."
|
|
1558
|
+
},
|
|
1559
|
+
"fingerprint": "kfp1:b11f292e89c1d1ed0de413d0c30a47a3f3e034679b695b5f5b7c5167f5f1e929",
|
|
1560
|
+
"baseline_state": "new"
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
"finding_id": "INJ-I-0024-1",
|
|
1564
|
+
"category": "sql_injection",
|
|
1565
|
+
"subtype": "string_concatenation",
|
|
1566
|
+
"title": "SQL Injection in perl-cgi/cgi-bin/order.cgi:13",
|
|
1567
|
+
"candidate_id": "I-0024",
|
|
1568
|
+
"location": {
|
|
1569
|
+
"file": "perl-cgi/cgi-bin/order.cgi",
|
|
1570
|
+
"start_line": 13
|
|
1571
|
+
},
|
|
1572
|
+
"evidence": [
|
|
1573
|
+
{
|
|
1574
|
+
"file": "perl-cgi/cgi-bin/order.cgi",
|
|
1575
|
+
"start_line": 13,
|
|
1576
|
+
"code": "my $sth = $dbh->prepare(\"SELECT * FROM orders WHERE id = $id\")",
|
|
1577
|
+
"why": "Unsanitized input concatenated into query",
|
|
1578
|
+
"line": 13
|
|
1579
|
+
}
|
|
1580
|
+
],
|
|
1581
|
+
"severity": "critical",
|
|
1582
|
+
"confidence": "high",
|
|
1583
|
+
"cwe": [
|
|
1584
|
+
"CWE-89"
|
|
1585
|
+
],
|
|
1586
|
+
"recommendation": "Use parameterized queries",
|
|
1587
|
+
"status": "confirmed",
|
|
1588
|
+
"verification": {
|
|
1589
|
+
"verdict": "confirmed",
|
|
1590
|
+
"reason_code": "traced",
|
|
1591
|
+
"checked": [
|
|
1592
|
+
"perl-cgi/cgi-bin/order.cgi line 13",
|
|
1593
|
+
"perl-cgi/cgi-bin/order.cgi for $id assignment",
|
|
1594
|
+
"perl-cgi/cgi-bin/order.cgi for $dbh->prepare usage"
|
|
1595
|
+
],
|
|
1596
|
+
"control_found": null,
|
|
1597
|
+
"notes": "The variable `$id` is taken directly from the request parameter and interpolated into the SQL string without integer casting or parameter binding. No sanitization or validation is performed before the query is executed. The SQL injection is confirmed.",
|
|
1598
|
+
"suggested_subtype": null,
|
|
1599
|
+
"suggested_cwe": null
|
|
1600
|
+
},
|
|
1601
|
+
"consensus": {
|
|
1602
|
+
"score": 0.95,
|
|
1603
|
+
"agreement_level": "unanimous",
|
|
1604
|
+
"voted_models": [
|
|
1605
|
+
"gpt-oss:20b"
|
|
1606
|
+
],
|
|
1607
|
+
"rationale": "No compensating control detected."
|
|
1608
|
+
},
|
|
1609
|
+
"fingerprint": "kfp1:0594761f603533b7391a9b7a8464e6865b71c1b61b71fdcd10991d861f51b5fb",
|
|
1610
|
+
"baseline_state": "new"
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
"finding_id": "INJ-I-0024-2",
|
|
1614
|
+
"category": "sql_injection",
|
|
1615
|
+
"subtype": "string_concatenation",
|
|
1616
|
+
"title": "SQL Injection in perl-cgi/cgi-bin/order.cgi:19",
|
|
1617
|
+
"candidate_id": "I-0024",
|
|
1618
|
+
"location": {
|
|
1619
|
+
"file": "perl-cgi/cgi-bin/order.cgi",
|
|
1620
|
+
"start_line": 19
|
|
1621
|
+
},
|
|
1622
|
+
"evidence": [
|
|
1623
|
+
{
|
|
1624
|
+
"file": "perl-cgi/cgi-bin/order.cgi",
|
|
1625
|
+
"start_line": 19,
|
|
1626
|
+
"code": "$dbh->do(\"DELETE FROM carts WHERE cart_id = \" . $q->param('cart'))",
|
|
1627
|
+
"why": "Unsanitized input concatenated into query",
|
|
1628
|
+
"line": 19
|
|
1629
|
+
}
|
|
1630
|
+
],
|
|
1631
|
+
"severity": "critical",
|
|
1632
|
+
"confidence": "high",
|
|
1633
|
+
"cwe": [
|
|
1634
|
+
"CWE-89"
|
|
1635
|
+
],
|
|
1636
|
+
"recommendation": "Use parameterized queries",
|
|
1637
|
+
"status": "confirmed",
|
|
1638
|
+
"verification": {
|
|
1639
|
+
"verdict": "confirmed",
|
|
1640
|
+
"reason_code": "traced",
|
|
1641
|
+
"checked": [
|
|
1642
|
+
"perl-cgi/cgi-bin/order.cgi line 19",
|
|
1643
|
+
"perl-cgi/cgi-bin/order.cgi for $q->param('cart') usage",
|
|
1644
|
+
"perl-cgi/cgi-bin/order.cgi for $dbh->do usage"
|
|
1645
|
+
],
|
|
1646
|
+
"control_found": null,
|
|
1647
|
+
"notes": "The `cart_id` value is concatenated directly into the DELETE statement without any sanitization or casting. The query is executed immediately, making the injection viable. Confirmed.",
|
|
1648
|
+
"suggested_subtype": null,
|
|
1649
|
+
"suggested_cwe": null
|
|
1650
|
+
},
|
|
1651
|
+
"consensus": {
|
|
1652
|
+
"score": 0.95,
|
|
1653
|
+
"agreement_level": "unanimous",
|
|
1654
|
+
"voted_models": [
|
|
1655
|
+
"gpt-oss:20b"
|
|
1656
|
+
],
|
|
1657
|
+
"rationale": "No sanitization or parameter binding found."
|
|
1658
|
+
},
|
|
1659
|
+
"fingerprint": "kfp1:c7206e5510c18d43418b9ed9a4d20047783ea5263cfdc395a138b735b613debc",
|
|
1660
|
+
"baseline_state": "new"
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
"finding_id": "INJ-I-0028-1",
|
|
1664
|
+
"category": "sql_injection",
|
|
1665
|
+
"subtype": "string_concatenation",
|
|
1666
|
+
"title": "SQL Injection in php-plain/admin.php:12",
|
|
1667
|
+
"candidate_id": "I-0028",
|
|
1668
|
+
"location": {
|
|
1669
|
+
"file": "php-plain/admin.php",
|
|
1670
|
+
"start_line": 12
|
|
1671
|
+
},
|
|
1672
|
+
"evidence": [
|
|
1673
|
+
{
|
|
1674
|
+
"file": "php-plain/admin.php",
|
|
1675
|
+
"start_line": 12,
|
|
1676
|
+
"code": "mysql_query(\"DELETE FROM users WHERE id = $id\")",
|
|
1677
|
+
"why": "Unsanitized input concatenated into query",
|
|
1678
|
+
"line": 12
|
|
1679
|
+
}
|
|
1680
|
+
],
|
|
1681
|
+
"severity": "critical",
|
|
1682
|
+
"confidence": "high",
|
|
1683
|
+
"cwe": [
|
|
1684
|
+
"CWE-89"
|
|
1685
|
+
],
|
|
1686
|
+
"recommendation": "Use parameterized queries",
|
|
1687
|
+
"status": "confirmed",
|
|
1688
|
+
"verification": {
|
|
1689
|
+
"verdict": "confirmed",
|
|
1690
|
+
"reason_code": "traced",
|
|
1691
|
+
"checked": [
|
|
1692
|
+
"php-plain/admin.php line 12",
|
|
1693
|
+
"php-plain/admin.php for $id assignment",
|
|
1694
|
+
"php-plain/admin.php for mysql_query usage"
|
|
1695
|
+
],
|
|
1696
|
+
"control_found": null,
|
|
1697
|
+
"notes": "The `id` parameter is taken from the request and concatenated into the SQL string without any escaping or casting. No prepared statement or sanitization is used. The injection is confirmed.",
|
|
1698
|
+
"suggested_subtype": null,
|
|
1699
|
+
"suggested_cwe": null
|
|
1700
|
+
},
|
|
1701
|
+
"consensus": {
|
|
1702
|
+
"score": 0.95,
|
|
1703
|
+
"agreement_level": "unanimous",
|
|
1704
|
+
"voted_models": [
|
|
1705
|
+
"gpt-oss:20b"
|
|
1706
|
+
],
|
|
1707
|
+
"rationale": "No mitigating control detected."
|
|
1708
|
+
},
|
|
1709
|
+
"fingerprint": "kfp1:59fca8cd0dedd8f70d286a5d3808292d9eddcf084148a63a448b7043b69c7948",
|
|
1710
|
+
"baseline_state": "new"
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
"finding_id": "INJ-I-0030-1",
|
|
1714
|
+
"category": "nosql_injection",
|
|
1715
|
+
"subtype": "operator_injection",
|
|
1716
|
+
"title": "NoSQL Injection in php-plain/mongo_find.php:6",
|
|
1717
|
+
"candidate_id": "I-0030",
|
|
1718
|
+
"location": {
|
|
1719
|
+
"file": "php-plain/mongo_find.php",
|
|
1720
|
+
"start_line": 6
|
|
1721
|
+
},
|
|
1722
|
+
"evidence": [
|
|
1723
|
+
{
|
|
1724
|
+
"file": "php-plain/mongo_find.php",
|
|
1725
|
+
"start_line": 6,
|
|
1726
|
+
"code": "$docs = $coll->find(array('$where' => \"this.name == '\" . $_GET['n'] . \"'\"));",
|
|
1727
|
+
"why": "Unsanitized input used in $where string",
|
|
1728
|
+
"line": 6
|
|
1729
|
+
}
|
|
1730
|
+
],
|
|
1731
|
+
"severity": "critical",
|
|
1732
|
+
"confidence": "high",
|
|
1733
|
+
"cwe": [
|
|
1734
|
+
"CWE-89"
|
|
1735
|
+
],
|
|
1736
|
+
"recommendation": "Avoid $where; use parameterized queries or safe filters",
|
|
1737
|
+
"status": "confirmed",
|
|
1738
|
+
"verification": {
|
|
1739
|
+
"verdict": "confirmed",
|
|
1740
|
+
"reason_code": "traced",
|
|
1741
|
+
"checked": [
|
|
1742
|
+
"php-plain/mongo_find.php line 6",
|
|
1743
|
+
"php-plain/mongo_find.php for $_GET['n'] usage",
|
|
1744
|
+
"php-plain/mongo_find.php for $coll->find usage"
|
|
1745
|
+
],
|
|
1746
|
+
"control_found": null,
|
|
1747
|
+
"notes": "The `$where` clause is constructed by concatenating the raw `$_GET['n']` value. No allow‑list or sanitization is applied, making the NoSQL injection viable. Confirmed.",
|
|
1748
|
+
"suggested_subtype": null,
|
|
1749
|
+
"suggested_cwe": null
|
|
1750
|
+
},
|
|
1751
|
+
"consensus": {
|
|
1752
|
+
"score": 0.95,
|
|
1753
|
+
"agreement_level": "unanimous",
|
|
1754
|
+
"voted_models": [
|
|
1755
|
+
"gpt-oss:20b"
|
|
1756
|
+
],
|
|
1757
|
+
"rationale": "No compensating control found."
|
|
1758
|
+
},
|
|
1759
|
+
"fingerprint": "kfp1:bf0197ac678eaca25ff151d805751c71b878e909d2e7180bacf39dd373b53b09",
|
|
1760
|
+
"baseline_state": "new"
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
"finding_id": "MASS-0011-1",
|
|
1764
|
+
"category": "mass_assignment",
|
|
1765
|
+
"subtype": "whole_object_binding",
|
|
1766
|
+
"title": "PHP profile update binds entire POST payload to user object",
|
|
1767
|
+
"candidate_id": "M-0011",
|
|
1768
|
+
"endpoint": null,
|
|
1769
|
+
"location": {
|
|
1770
|
+
"file": "php-plain/profile.php",
|
|
1771
|
+
"start_line": 5,
|
|
1772
|
+
"end_line": 11
|
|
1773
|
+
},
|
|
1774
|
+
"evidence": [
|
|
1775
|
+
{
|
|
1776
|
+
"file": "php-plain/profile.php",
|
|
1777
|
+
"line": 6,
|
|
1778
|
+
"code": "foreach ($_POST as $field => $value) { $user->$field = $value; }",
|
|
1779
|
+
"why": "binds all request fields"
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
"file": "php-plain/profile.php",
|
|
1783
|
+
"line": 10,
|
|
1784
|
+
"code": "$user->role = $_POST['role'];",
|
|
1785
|
+
"why": "privileged field bound"
|
|
1786
|
+
}
|
|
1787
|
+
],
|
|
1788
|
+
"attack_vector": {
|
|
1789
|
+
"method": "POST",
|
|
1790
|
+
"path": "/php-plain/profile.php",
|
|
1791
|
+
"fields": {
|
|
1792
|
+
"role": "admin"
|
|
1793
|
+
},
|
|
1794
|
+
"readback": "GET /php-plain/profile.php"
|
|
1795
|
+
},
|
|
1796
|
+
"impact": "Privilege escalation via role field.",
|
|
1797
|
+
"severity": "critical",
|
|
1798
|
+
"confidence": "high",
|
|
1799
|
+
"cwe": [
|
|
1800
|
+
"CWE-915"
|
|
1801
|
+
],
|
|
1802
|
+
"owasp": [
|
|
1803
|
+
"API3:2023"
|
|
1804
|
+
],
|
|
1805
|
+
"recommendation": "Copy only allowed fields from the request.",
|
|
1806
|
+
"fix_example": "$user->name = $_POST['name']; $user->email = $_POST['email'];",
|
|
1807
|
+
"status": "confirmed",
|
|
1808
|
+
"verification": {
|
|
1809
|
+
"verdict": "confirmed",
|
|
1810
|
+
"reason_code": "traced",
|
|
1811
|
+
"checked": [
|
|
1812
|
+
"php-plain/profile.php",
|
|
1813
|
+
"inc/auth.php",
|
|
1814
|
+
"inc/db.php"
|
|
1815
|
+
],
|
|
1816
|
+
"control_found": null,
|
|
1817
|
+
"notes": "The code loops over $_POST and assigns every field to the user object, including the privileged 'role' field, with no allow‑list or filtering.",
|
|
1818
|
+
"suggested_subtype": null,
|
|
1819
|
+
"suggested_cwe": null
|
|
1820
|
+
},
|
|
1821
|
+
"consensus": {
|
|
1822
|
+
"score": 0.95,
|
|
1823
|
+
"agreement_level": "unanimous",
|
|
1824
|
+
"voted_models": [
|
|
1825
|
+
"gpt-oss:20b"
|
|
1826
|
+
],
|
|
1827
|
+
"rationale": "Full payload binding without field restrictions."
|
|
1828
|
+
},
|
|
1829
|
+
"fingerprint": "kfp1:4042bba4357c8075f2a2bd67fdfc0cea2d5512d99de70a4395a9bfcf87b67063",
|
|
1830
|
+
"baseline_state": "new"
|
|
1831
|
+
},
|
|
1832
|
+
{
|
|
1833
|
+
"finding_id": "MASS-0012-1",
|
|
1834
|
+
"category": "mass_assignment",
|
|
1835
|
+
"subtype": "privileged_field_writable",
|
|
1836
|
+
"title": "PHP profile update allows client to set role",
|
|
1837
|
+
"candidate_id": "M-0012",
|
|
1838
|
+
"endpoint": null,
|
|
1839
|
+
"location": {
|
|
1840
|
+
"file": "php-plain/profile.php",
|
|
1841
|
+
"start_line": 5,
|
|
1842
|
+
"end_line": 11
|
|
1843
|
+
},
|
|
1844
|
+
"evidence": [
|
|
1845
|
+
{
|
|
1846
|
+
"file": "php-plain/profile.php",
|
|
1847
|
+
"line": 10,
|
|
1848
|
+
"code": "$user->role = $_POST['role'];",
|
|
1849
|
+
"why": "privileged field writable"
|
|
1850
|
+
}
|
|
1851
|
+
],
|
|
1852
|
+
"attack_vector": {
|
|
1853
|
+
"method": "POST",
|
|
1854
|
+
"path": "/php-plain/profile.php",
|
|
1855
|
+
"fields": {
|
|
1856
|
+
"role": "admin"
|
|
1857
|
+
},
|
|
1858
|
+
"readback": "GET /php-plain/profile.php"
|
|
1859
|
+
},
|
|
1860
|
+
"impact": "Privilege escalation via role field.",
|
|
1861
|
+
"severity": "critical",
|
|
1862
|
+
"confidence": "high",
|
|
1863
|
+
"cwe": [
|
|
1864
|
+
"CWE-915",
|
|
1865
|
+
"CWE-269"
|
|
1866
|
+
],
|
|
1867
|
+
"owasp": [
|
|
1868
|
+
"API3:2023"
|
|
1869
|
+
],
|
|
1870
|
+
"recommendation": "Remove role field from client input and enforce server‑side role check.",
|
|
1871
|
+
"fix_example": "$user->role = $_SESSION['role'];",
|
|
1872
|
+
"status": "unverified",
|
|
1873
|
+
"fingerprint": "kfp1:c0564ee6e15360b75abcad854b22f5c063ad4eb320bd9ce0a4c47e3b7e483d2b",
|
|
1874
|
+
"baseline_state": "new"
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
"finding_id": "BOLA-R-0001-1",
|
|
1878
|
+
"category": "bola",
|
|
1879
|
+
"subtype": "missing_ownership_check",
|
|
1880
|
+
"title": "BOLA in ANY /user/edit.do: missing owner verification",
|
|
1881
|
+
"route_id": "R-0001",
|
|
1882
|
+
"endpoint": {
|
|
1883
|
+
"method": "ANY",
|
|
1884
|
+
"path": "/user/edit.do"
|
|
1885
|
+
},
|
|
1886
|
+
"location": {
|
|
1887
|
+
"file": "java-legacy/src/com/acme/struts/EditUserAction.java",
|
|
1888
|
+
"start_line": 13
|
|
1889
|
+
},
|
|
1890
|
+
"evidence": [
|
|
1891
|
+
{
|
|
1892
|
+
"file": "java-legacy/src/com/acme/struts/EditUserAction.java",
|
|
1893
|
+
"start_line": 13,
|
|
1894
|
+
"code": "PreparedStatement ps = conn.prepareStatement(\"UPDATE users SET email = '\" + email + \"' WHERE id = \" + userId);",
|
|
1895
|
+
"why": "The user ID is taken from the request and no ownership check is performed.",
|
|
1896
|
+
"line": 13
|
|
1897
|
+
}
|
|
1898
|
+
],
|
|
1899
|
+
"severity": "high",
|
|
1900
|
+
"confidence": "high",
|
|
1901
|
+
"cwe": [
|
|
1902
|
+
"CWE-639"
|
|
1903
|
+
],
|
|
1904
|
+
"recommendation": "Add a WHERE clause that binds the user to the current session user.",
|
|
1905
|
+
"status": "unverified",
|
|
1906
|
+
"fingerprint": "kfp1:d33dad00f935095b6fc638221a95cf42ca6b5ecaa654407c8a976b3b4a77efda",
|
|
1907
|
+
"baseline_state": "new"
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
"finding_id": "BOLA-R-0002-1",
|
|
1911
|
+
"category": "bola",
|
|
1912
|
+
"subtype": "missing_ownership_check",
|
|
1913
|
+
"title": "BOLA in ANY /classic-asp/shop/member.asp: missing owner verification (edit)",
|
|
1914
|
+
"route_id": "R-0002",
|
|
1915
|
+
"endpoint": {
|
|
1916
|
+
"method": "ANY",
|
|
1917
|
+
"path": "/classic-asp/shop/member.asp"
|
|
1918
|
+
},
|
|
1919
|
+
"location": {
|
|
1920
|
+
"file": "classic-asp/shop/member.asp",
|
|
1921
|
+
"start_line": 6
|
|
1922
|
+
},
|
|
1923
|
+
"evidence": [
|
|
1924
|
+
{
|
|
1925
|
+
"file": "classic-asp/shop/member.asp",
|
|
1926
|
+
"start_line": 6,
|
|
1927
|
+
"code": "sql = \"UPDATE members SET name = '\" & Replace(Request.Form(\"name\"), \"'\", \"''\") & \"'\"",
|
|
1928
|
+
"why": "ID comes from Request.Form(\"member_id\") and is used without checking ownership.",
|
|
1929
|
+
"line": 6
|
|
1930
|
+
},
|
|
1931
|
+
{
|
|
1932
|
+
"file": "classic-asp/shop/member.asp",
|
|
1933
|
+
"start_line": 7,
|
|
1934
|
+
"code": "sql = sql & \" WHERE member_id = \" & Request.Form(\"member_id\")",
|
|
1935
|
+
"why": "No reference to the current session user; any logged‑in user can update any member.",
|
|
1936
|
+
"line": 7
|
|
1937
|
+
}
|
|
1938
|
+
],
|
|
1939
|
+
"severity": "high",
|
|
1940
|
+
"confidence": "high",
|
|
1941
|
+
"cwe": [
|
|
1942
|
+
"CWE-639"
|
|
1943
|
+
],
|
|
1944
|
+
"recommendation": "Bind the UPDATE to the current session user.",
|
|
1945
|
+
"status": "confirmed",
|
|
1946
|
+
"verification": {
|
|
1947
|
+
"verdict": "confirmed",
|
|
1948
|
+
"reason_code": "traced",
|
|
1949
|
+
"checked": [
|
|
1950
|
+
"classic-asp/shop/member.asp lines 6-7",
|
|
1951
|
+
"checked for global includes and session checks – none found"
|
|
1952
|
+
],
|
|
1953
|
+
"control_found": null,
|
|
1954
|
+
"notes": "The UPDATE statement uses Request.Form(\"member_id\") directly without any reference to the current session user. No global filter or session‑based ownership check is present.",
|
|
1955
|
+
"suggested_subtype": null,
|
|
1956
|
+
"suggested_cwe": null
|
|
1957
|
+
},
|
|
1958
|
+
"consensus": {
|
|
1959
|
+
"score": 0.95,
|
|
1960
|
+
"agreement_level": "unanimous",
|
|
1961
|
+
"voted_models": [
|
|
1962
|
+
"gpt-oss:20b"
|
|
1963
|
+
],
|
|
1964
|
+
"rationale": "Direct inspection of the ASP file shows no ownership guard."
|
|
1965
|
+
},
|
|
1966
|
+
"fingerprint": "kfp1:14b20124aac382d333e11fb45cef1ee5c8e212e24f93ee6e2dfc5c81397d2c0f",
|
|
1967
|
+
"baseline_state": "new"
|
|
1968
|
+
},
|
|
1969
|
+
{
|
|
1970
|
+
"finding_id": "BOLA-R-0003-1",
|
|
1971
|
+
"category": "bola",
|
|
1972
|
+
"subtype": "missing_ownership_check",
|
|
1973
|
+
"title": "BOLA in ANY /classic-asp/shop/member.asp: missing owner verification (delete)",
|
|
1974
|
+
"route_id": "R-0003",
|
|
1975
|
+
"endpoint": {
|
|
1976
|
+
"method": "ANY",
|
|
1977
|
+
"path": "/classic-asp/shop/member.asp"
|
|
1978
|
+
},
|
|
1979
|
+
"location": {
|
|
1980
|
+
"file": "classic-asp/shop/member.asp",
|
|
1981
|
+
"start_line": 10
|
|
1982
|
+
},
|
|
1983
|
+
"evidence": [
|
|
1984
|
+
{
|
|
1985
|
+
"file": "classic-asp/shop/member.asp",
|
|
1986
|
+
"start_line": 10,
|
|
1987
|
+
"code": "conn.Execute \"DELETE FROM members WHERE member_id = \" & Request.QueryString(\"member_id\")",
|
|
1988
|
+
"why": "The member_id is taken from the query string and no ownership check is performed.",
|
|
1989
|
+
"line": 10
|
|
1990
|
+
}
|
|
1991
|
+
],
|
|
1992
|
+
"severity": "high",
|
|
1993
|
+
"confidence": "high",
|
|
1994
|
+
"cwe": [
|
|
1995
|
+
"CWE-639"
|
|
1996
|
+
],
|
|
1997
|
+
"recommendation": "Add ownership check to the DELETE statement.",
|
|
1998
|
+
"status": "confirmed",
|
|
1999
|
+
"verification": {
|
|
2000
|
+
"verdict": "confirmed",
|
|
2001
|
+
"reason_code": "traced",
|
|
2002
|
+
"checked": [
|
|
2003
|
+
"classic-asp/shop/member.asp line 10",
|
|
2004
|
+
"checked for global includes and session checks – none found"
|
|
2005
|
+
],
|
|
2006
|
+
"control_found": null,
|
|
2007
|
+
"notes": "The DELETE statement uses Request.QueryString(\"member_id\") without any ownership verification. No compensating control is present.",
|
|
2008
|
+
"suggested_subtype": null,
|
|
2009
|
+
"suggested_cwe": null
|
|
2010
|
+
},
|
|
2011
|
+
"consensus": {
|
|
2012
|
+
"score": 0.95,
|
|
2013
|
+
"agreement_level": "unanimous",
|
|
2014
|
+
"voted_models": [
|
|
2015
|
+
"gpt-oss:20b"
|
|
2016
|
+
],
|
|
2017
|
+
"rationale": "No ownership check is visible in the code."
|
|
2018
|
+
},
|
|
2019
|
+
"fingerprint": "kfp1:8d4a9649ef5cdb67d760d7c7ab1404415ddc2fbf9065f0e7957148855d96cdd9",
|
|
2020
|
+
"baseline_state": "new"
|
|
2021
|
+
},
|
|
2022
|
+
{
|
|
2023
|
+
"finding_id": "BOLA-R-0004-1",
|
|
2024
|
+
"category": "bola",
|
|
2025
|
+
"subtype": "missing_ownership_check",
|
|
2026
|
+
"title": "BOLA in ANY /classic-asp/shop/order.asp: missing owner verification (read)",
|
|
2027
|
+
"route_id": "R-0004",
|
|
2028
|
+
"endpoint": {
|
|
2029
|
+
"method": "ANY",
|
|
2030
|
+
"path": "/classic-asp/shop/order.asp"
|
|
2031
|
+
},
|
|
2032
|
+
"location": {
|
|
2033
|
+
"file": "classic-asp/shop/order.asp",
|
|
2034
|
+
"start_line": 6
|
|
2035
|
+
},
|
|
2036
|
+
"evidence": [
|
|
2037
|
+
{
|
|
2038
|
+
"file": "classic-asp/shop/order.asp",
|
|
2039
|
+
"start_line": 6,
|
|
2040
|
+
"code": "Set rs = conn.Execute(\"SELECT * FROM orders WHERE id = \" & id)",
|
|
2041
|
+
"why": "The query returns order data without filtering by the current user.",
|
|
2042
|
+
"line": 6
|
|
2043
|
+
}
|
|
2044
|
+
],
|
|
2045
|
+
"severity": "high",
|
|
2046
|
+
"confidence": "high",
|
|
2047
|
+
"cwe": [
|
|
2048
|
+
"CWE-639"
|
|
2049
|
+
],
|
|
2050
|
+
"recommendation": "Filter the SELECT by the current session user.",
|
|
2051
|
+
"status": "confirmed",
|
|
2052
|
+
"verification": {
|
|
2053
|
+
"verdict": "confirmed",
|
|
2054
|
+
"reason_code": "traced",
|
|
2055
|
+
"checked": [
|
|
2056
|
+
"classic-asp/shop/order.asp line 6",
|
|
2057
|
+
"checked for global includes and session checks – none found"
|
|
2058
|
+
],
|
|
2059
|
+
"control_found": null,
|
|
2060
|
+
"notes": "The SELECT statement returns order data solely based on the supplied id. No filter on the current user or tenant is applied.",
|
|
2061
|
+
"suggested_subtype": null,
|
|
2062
|
+
"suggested_cwe": null
|
|
2063
|
+
},
|
|
2064
|
+
"consensus": {
|
|
2065
|
+
"score": 0.95,
|
|
2066
|
+
"agreement_level": "unanimous",
|
|
2067
|
+
"voted_models": [
|
|
2068
|
+
"gpt-oss:20b"
|
|
2069
|
+
],
|
|
2070
|
+
"rationale": "Ownership guard is absent."
|
|
2071
|
+
},
|
|
2072
|
+
"fingerprint": "kfp1:ef95b66c53a33e34324095841d6cfabed5b7c5a8e7d404195c87347f362d744b",
|
|
2073
|
+
"baseline_state": "new"
|
|
2074
|
+
},
|
|
2075
|
+
{
|
|
2076
|
+
"finding_id": "BOLA-R-0005-1",
|
|
2077
|
+
"category": "bola",
|
|
2078
|
+
"subtype": "missing_ownership_check",
|
|
2079
|
+
"title": "BOLA in ANY /coldfusion/app/OrderService.cfc: missing owner verification (read)",
|
|
2080
|
+
"route_id": "R-0005",
|
|
2081
|
+
"endpoint": {
|
|
2082
|
+
"method": "ANY",
|
|
2083
|
+
"path": "/coldfusion/app/OrderService.cfc"
|
|
2084
|
+
},
|
|
2085
|
+
"location": {
|
|
2086
|
+
"file": "coldfusion/app/OrderService.cfc",
|
|
2087
|
+
"start_line": 5
|
|
2088
|
+
},
|
|
2089
|
+
"evidence": [
|
|
2090
|
+
{
|
|
2091
|
+
"file": "coldfusion/app/OrderService.cfc",
|
|
2092
|
+
"start_line": 5,
|
|
2093
|
+
"code": "SELECT * FROM orders WHERE id = #arguments.orderId#",
|
|
2094
|
+
"why": "The order ID is taken from the request and no ownership check is performed.",
|
|
2095
|
+
"line": 5
|
|
2096
|
+
}
|
|
2097
|
+
],
|
|
2098
|
+
"severity": "high",
|
|
2099
|
+
"confidence": "high",
|
|
2100
|
+
"cwe": [
|
|
2101
|
+
"CWE-639"
|
|
2102
|
+
],
|
|
2103
|
+
"recommendation": "Add a WHERE clause that binds the order to the current session user.",
|
|
2104
|
+
"status": "confirmed",
|
|
2105
|
+
"verification": {
|
|
2106
|
+
"verdict": "confirmed",
|
|
2107
|
+
"reason_code": "traced",
|
|
2108
|
+
"checked": [
|
|
2109
|
+
"coldfusion/app/OrderService.cfc line 5",
|
|
2110
|
+
"checked for session checks in the CFC – none found"
|
|
2111
|
+
],
|
|
2112
|
+
"control_found": null,
|
|
2113
|
+
"notes": "The query interpolates #arguments.orderId# directly. No session‑based ownership check or role guard is present.",
|
|
2114
|
+
"suggested_subtype": null,
|
|
2115
|
+
"suggested_cwe": null
|
|
2116
|
+
},
|
|
2117
|
+
"consensus": {
|
|
2118
|
+
"score": 0.95,
|
|
2119
|
+
"agreement_level": "unanimous",
|
|
2120
|
+
"voted_models": [
|
|
2121
|
+
"gpt-oss:20b"
|
|
2122
|
+
],
|
|
2123
|
+
"rationale": "No ownership verification is visible."
|
|
2124
|
+
},
|
|
2125
|
+
"fingerprint": "kfp1:cd632f58ca086747bfedd17a9708c0f51fb43e74ccebaa2ce2eef3cb4db60960",
|
|
2126
|
+
"baseline_state": "new"
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
"finding_id": "BOLA-R-0006-1",
|
|
2130
|
+
"category": "bola",
|
|
2131
|
+
"subtype": "missing_ownership_check",
|
|
2132
|
+
"title": "BOLA in ANY /coldfusion/app/order.cfm: missing owner verification (read)",
|
|
2133
|
+
"route_id": "R-0006",
|
|
2134
|
+
"endpoint": {
|
|
2135
|
+
"method": "ANY",
|
|
2136
|
+
"path": "/coldfusion/app/order.cfm"
|
|
2137
|
+
},
|
|
2138
|
+
"location": {
|
|
2139
|
+
"file": "coldfusion/app/order.cfm",
|
|
2140
|
+
"start_line": 3
|
|
2141
|
+
},
|
|
2142
|
+
"evidence": [
|
|
2143
|
+
{
|
|
2144
|
+
"file": "coldfusion/app/order.cfm",
|
|
2145
|
+
"start_line": 3,
|
|
2146
|
+
"code": "SELECT * FROM orders WHERE id = #url.id#",
|
|
2147
|
+
"why": "The query returns order data without filtering by the current user.",
|
|
2148
|
+
"line": 3
|
|
2149
|
+
}
|
|
2150
|
+
],
|
|
2151
|
+
"severity": "high",
|
|
2152
|
+
"confidence": "high",
|
|
2153
|
+
"cwe": [
|
|
2154
|
+
"CWE-639"
|
|
2155
|
+
],
|
|
2156
|
+
"recommendation": "Bind the SELECT to the current session user.",
|
|
2157
|
+
"status": "confirmed",
|
|
2158
|
+
"verification": {
|
|
2159
|
+
"verdict": "confirmed",
|
|
2160
|
+
"reason_code": "traced",
|
|
2161
|
+
"checked": [
|
|
2162
|
+
"coldfusion/app/order.cfm line 3",
|
|
2163
|
+
"checked for session checks in the CFML page – none found"
|
|
2164
|
+
],
|
|
2165
|
+
"control_found": null,
|
|
2166
|
+
"notes": "The SELECT statement uses #url.id# without any reference to the current user. No compensating control exists.",
|
|
2167
|
+
"suggested_subtype": null,
|
|
2168
|
+
"suggested_cwe": null
|
|
2169
|
+
},
|
|
2170
|
+
"consensus": {
|
|
2171
|
+
"score": 0.95,
|
|
2172
|
+
"agreement_level": "unanimous",
|
|
2173
|
+
"voted_models": [
|
|
2174
|
+
"gpt-oss:20b"
|
|
2175
|
+
],
|
|
2176
|
+
"rationale": "Direct inspection shows no ownership guard."
|
|
2177
|
+
},
|
|
2178
|
+
"fingerprint": "kfp1:6335d2fd67b9a9d599d1b76b5eb5f8680a33d2c4c3eb5bceb5461fc467168975",
|
|
2179
|
+
"baseline_state": "new"
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
"finding_id": "BOLA-R-0007-1",
|
|
2183
|
+
"category": "bola",
|
|
2184
|
+
"subtype": "missing_ownership_check",
|
|
2185
|
+
"title": "BOLA in POST /Admin/DeleteUser: missing owner verification",
|
|
2186
|
+
"route_id": "R-0007",
|
|
2187
|
+
"endpoint": {
|
|
2188
|
+
"method": "POST",
|
|
2189
|
+
"path": "/Admin/DeleteUser"
|
|
2190
|
+
},
|
|
2191
|
+
"location": {
|
|
2192
|
+
"file": "dotnet-legacy/Web/Controllers/AdminController.cs",
|
|
2193
|
+
"start_line": 14
|
|
2194
|
+
},
|
|
2195
|
+
"evidence": [
|
|
2196
|
+
{
|
|
2197
|
+
"file": "dotnet-legacy/Web/Controllers/AdminController.cs",
|
|
2198
|
+
"start_line": 14,
|
|
2199
|
+
"code": "var u = db.Users.Find(id);",
|
|
2200
|
+
"why": "The user ID is taken from the request and no ownership or role check is performed.",
|
|
2201
|
+
"line": 14
|
|
2202
|
+
}
|
|
2203
|
+
],
|
|
2204
|
+
"severity": "high",
|
|
2205
|
+
"confidence": "high",
|
|
2206
|
+
"cwe": [
|
|
2207
|
+
"CWE-639"
|
|
2208
|
+
],
|
|
2209
|
+
"recommendation": "Require admin role or check that the current user is allowed to delete the target user.",
|
|
2210
|
+
"status": "confirmed",
|
|
2211
|
+
"verification": {
|
|
2212
|
+
"verdict": "confirmed",
|
|
2213
|
+
"reason_code": "traced",
|
|
2214
|
+
"checked": [
|
|
2215
|
+
"dotnet-legacy/Web/Controllers/AdminController.cs line 14",
|
|
2216
|
+
"checked for [Authorize] attributes – present but no role restriction",
|
|
2217
|
+
"checked FilterConfig.cs – no global admin filter"
|
|
2218
|
+
],
|
|
2219
|
+
"control_found": null,
|
|
2220
|
+
"notes": "The action is only protected by [Authorize], allowing any authenticated user to delete any user. No role or ownership check is performed.",
|
|
2221
|
+
"suggested_subtype": null,
|
|
2222
|
+
"suggested_cwe": null
|
|
2223
|
+
},
|
|
2224
|
+
"consensus": {
|
|
2225
|
+
"score": 0.95,
|
|
2226
|
+
"agreement_level": "unanimous",
|
|
2227
|
+
"voted_models": [
|
|
2228
|
+
"gpt-oss:20b"
|
|
2229
|
+
],
|
|
2230
|
+
"rationale": "Ownership guard is missing."
|
|
2231
|
+
},
|
|
2232
|
+
"fingerprint": "kfp1:cf7445db9d834153c69eaec04070033bdffb4366eec1700c3adea8b1d9afe1cd",
|
|
2233
|
+
"baseline_state": "new"
|
|
2234
|
+
},
|
|
2235
|
+
{
|
|
2236
|
+
"finding_id": "BOLA-R-0008-1",
|
|
2237
|
+
"category": "bola",
|
|
2238
|
+
"subtype": "missing_ownership_check",
|
|
2239
|
+
"title": "BOLA in ANY /Admin/Export: missing owner verification",
|
|
2240
|
+
"route_id": "R-0008",
|
|
2241
|
+
"endpoint": {
|
|
2242
|
+
"method": "ANY",
|
|
2243
|
+
"path": "/Admin/Export"
|
|
2244
|
+
},
|
|
2245
|
+
"location": {
|
|
2246
|
+
"file": "dotnet-legacy/Web/Controllers/AdminController.cs",
|
|
2247
|
+
"start_line": 32
|
|
2248
|
+
},
|
|
2249
|
+
"evidence": [
|
|
2250
|
+
{
|
|
2251
|
+
"file": "dotnet-legacy/Web/Controllers/AdminController.cs",
|
|
2252
|
+
"start_line": 32,
|
|
2253
|
+
"code": "if (Request.QueryString[\"isAdmin\"] == \"true\") { return Json(db.Users.ToList(), JsonRequestBehavior.AllowGet); }",
|
|
2254
|
+
"why": "Any authenticated user can set isAdmin=true and retrieve all users.",
|
|
2255
|
+
"line": 32
|
|
2256
|
+
}
|
|
2257
|
+
],
|
|
2258
|
+
"severity": "high",
|
|
2259
|
+
"confidence": "high",
|
|
2260
|
+
"cwe": [
|
|
2261
|
+
"CWE-639"
|
|
2262
|
+
],
|
|
2263
|
+
"recommendation": "Remove the isAdmin flag and enforce admin role.",
|
|
2264
|
+
"status": "confirmed",
|
|
2265
|
+
"verification": {
|
|
2266
|
+
"verdict": "confirmed",
|
|
2267
|
+
"reason_code": "traced",
|
|
2268
|
+
"checked": [
|
|
2269
|
+
"dotnet-legacy/Web/Controllers/AdminController.cs line 32",
|
|
2270
|
+
"checked for [Authorize] attributes – present but no role restriction",
|
|
2271
|
+
"checked FilterConfig.cs – no global admin filter"
|
|
2272
|
+
],
|
|
2273
|
+
"control_found": null,
|
|
2274
|
+
"notes": "Any authenticated user can set isAdmin=true and retrieve all users. No role guard is present.",
|
|
2275
|
+
"suggested_subtype": null,
|
|
2276
|
+
"suggested_cwe": null
|
|
2277
|
+
},
|
|
2278
|
+
"consensus": {
|
|
2279
|
+
"score": 0.95,
|
|
2280
|
+
"agreement_level": "unanimous",
|
|
2281
|
+
"voted_models": [
|
|
2282
|
+
"gpt-oss:20b"
|
|
2283
|
+
],
|
|
2284
|
+
"rationale": "Missing admin role enforcement."
|
|
2285
|
+
},
|
|
2286
|
+
"fingerprint": "kfp1:90078c439bd27aeab889e0707674ba50629733ab91ffef7dffb8a323e80779b3",
|
|
2287
|
+
"baseline_state": "new"
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
"finding_id": "BOLA-R-0009-1",
|
|
2291
|
+
"category": "bola",
|
|
2292
|
+
"subtype": "missing_ownership_check",
|
|
2293
|
+
"title": "BOLA in ANY /Admin/Export: missing owner verification",
|
|
2294
|
+
"route_id": "R-0009",
|
|
2295
|
+
"endpoint": {
|
|
2296
|
+
"method": "ANY",
|
|
2297
|
+
"path": "/Admin/Export"
|
|
2298
|
+
},
|
|
2299
|
+
"location": {
|
|
2300
|
+
"file": "dotnet-legacy/Web/Controllers/AdminController.cs",
|
|
2301
|
+
"start_line": 32
|
|
2302
|
+
},
|
|
2303
|
+
"evidence": [
|
|
2304
|
+
{
|
|
2305
|
+
"file": "dotnet-legacy/Web/Controllers/AdminController.cs",
|
|
2306
|
+
"start_line": 32,
|
|
2307
|
+
"code": "if (Request.QueryString[\"isAdmin\"] == \"true\") { return Json(db.Users.ToList(), JsonRequestBehavior.AllowGet); }",
|
|
2308
|
+
"why": "Any authenticated user can set isAdmin=true and retrieve all users.",
|
|
2309
|
+
"line": 32
|
|
2310
|
+
}
|
|
2311
|
+
],
|
|
2312
|
+
"severity": "high",
|
|
2313
|
+
"confidence": "high",
|
|
2314
|
+
"cwe": [
|
|
2315
|
+
"CWE-639"
|
|
2316
|
+
],
|
|
2317
|
+
"recommendation": "Remove the isAdmin flag and enforce admin role.",
|
|
2318
|
+
"status": "confirmed",
|
|
2319
|
+
"verification": {
|
|
2320
|
+
"verdict": "confirmed",
|
|
2321
|
+
"reason_code": "traced",
|
|
2322
|
+
"checked": [
|
|
2323
|
+
"dotnet-legacy/Web/Controllers/AdminController.cs line 32",
|
|
2324
|
+
"checked for [Authorize] attributes – present but no role restriction",
|
|
2325
|
+
"checked FilterConfig.cs – no global admin filter"
|
|
2326
|
+
],
|
|
2327
|
+
"control_found": null,
|
|
2328
|
+
"notes": "Same as BOLA-R-0008-1 – any authenticated user can retrieve all users via the isAdmin flag.",
|
|
2329
|
+
"suggested_subtype": null,
|
|
2330
|
+
"suggested_cwe": null
|
|
2331
|
+
},
|
|
2332
|
+
"consensus": {
|
|
2333
|
+
"score": 0.95,
|
|
2334
|
+
"agreement_level": "unanimous",
|
|
2335
|
+
"voted_models": [
|
|
2336
|
+
"gpt-oss:20b"
|
|
2337
|
+
],
|
|
2338
|
+
"rationale": "Missing admin role enforcement."
|
|
2339
|
+
},
|
|
2340
|
+
"fingerprint": "kfp1:b05dea74e289c89674339e0435480cdc751c9b47659ea1d3de9c3b361d8e7809",
|
|
2341
|
+
"baseline_state": "new"
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
"finding_id": "BOLA-R-0010-1",
|
|
2345
|
+
"category": "bola",
|
|
2346
|
+
"subtype": "missing_ownership_check",
|
|
2347
|
+
"title": "BOLA in POST /Customer/Edit: missing owner verification",
|
|
2348
|
+
"route_id": "R-0010",
|
|
2349
|
+
"endpoint": {
|
|
2350
|
+
"method": "POST",
|
|
2351
|
+
"path": "/Customer/Edit"
|
|
2352
|
+
},
|
|
2353
|
+
"location": {
|
|
2354
|
+
"file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
|
|
2355
|
+
"start_line": 12
|
|
2356
|
+
},
|
|
2357
|
+
"evidence": [
|
|
2358
|
+
{
|
|
2359
|
+
"file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
|
|
2360
|
+
"start_line": 12,
|
|
2361
|
+
"code": "db.Entry(customer).State = System.Data.Entity.EntityState.Modified;",
|
|
2362
|
+
"why": "The Customer object is bound from the request and its Id is used directly to modify the record without checking that the Id belongs to the authenticated user.",
|
|
2363
|
+
"line": 12
|
|
2364
|
+
}
|
|
2365
|
+
],
|
|
2366
|
+
"severity": "high",
|
|
2367
|
+
"confidence": "high",
|
|
2368
|
+
"cwe": [
|
|
2369
|
+
"CWE-284",
|
|
2370
|
+
"CWE-639"
|
|
2371
|
+
],
|
|
2372
|
+
"recommendation": "Load the customer by Id and verify that its OwnerId matches the current user before saving.",
|
|
2373
|
+
"status": "confirmed",
|
|
2374
|
+
"verification": {
|
|
2375
|
+
"verdict": "confirmed",
|
|
2376
|
+
"reason_code": "traced",
|
|
2377
|
+
"checked": [
|
|
2378
|
+
"dotnet-legacy/Web/Controllers/CustomerController.cs: lines 12-15",
|
|
2379
|
+
"dotnet-legacy/Web/Filters/AuthorizationFilter.cs: no ownership enforcement"
|
|
2380
|
+
],
|
|
2381
|
+
"control_found": null,
|
|
2382
|
+
"notes": "The controller action marks the entity as Modified and saves it without checking that the Customer.OwnerId matches the logged‑in user. No global filter or attribute performs this check.",
|
|
2383
|
+
"suggested_subtype": null,
|
|
2384
|
+
"suggested_cwe": null
|
|
2385
|
+
},
|
|
2386
|
+
"consensus": {
|
|
2387
|
+
"score": 0.95,
|
|
2388
|
+
"agreement_level": "unanimous",
|
|
2389
|
+
"voted_models": [
|
|
2390
|
+
"gpt-oss:20b"
|
|
2391
|
+
],
|
|
2392
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
2393
|
+
},
|
|
2394
|
+
"fingerprint": "kfp1:2ff50be6396a3261e2543aa3b955d99113acdc748a014446cb0b489407bafe90",
|
|
2395
|
+
"baseline_state": "new"
|
|
2396
|
+
},
|
|
2397
|
+
{
|
|
2398
|
+
"finding_id": "BOLA-R-0011-1",
|
|
2399
|
+
"category": "bola",
|
|
2400
|
+
"subtype": "missing_ownership_check",
|
|
2401
|
+
"title": "BOLA in POST /Customer/EditName: missing owner verification",
|
|
2402
|
+
"route_id": "R-0011",
|
|
2403
|
+
"endpoint": {
|
|
2404
|
+
"method": "POST",
|
|
2405
|
+
"path": "/Customer/EditName"
|
|
2406
|
+
},
|
|
2407
|
+
"location": {
|
|
2408
|
+
"file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
|
|
2409
|
+
"start_line": 20
|
|
2410
|
+
},
|
|
2411
|
+
"evidence": [
|
|
2412
|
+
{
|
|
2413
|
+
"file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
|
|
2414
|
+
"start_line": 20,
|
|
2415
|
+
"code": "db.SaveChanges();",
|
|
2416
|
+
"why": "The Customer object is bound from the request and its Id is used directly to modify the record without verifying ownership.",
|
|
2417
|
+
"line": 20
|
|
2418
|
+
}
|
|
2419
|
+
],
|
|
2420
|
+
"severity": "high",
|
|
2421
|
+
"confidence": "high",
|
|
2422
|
+
"cwe": [
|
|
2423
|
+
"CWE-284",
|
|
2424
|
+
"CWE-639"
|
|
2425
|
+
],
|
|
2426
|
+
"recommendation": "Verify that the Customer.Id belongs to the authenticated user before calling SaveChanges.",
|
|
2427
|
+
"status": "confirmed",
|
|
2428
|
+
"verification": {
|
|
2429
|
+
"verdict": "confirmed",
|
|
2430
|
+
"reason_code": "traced",
|
|
2431
|
+
"checked": [
|
|
2432
|
+
"dotnet-legacy/Web/Controllers/CustomerController.cs: lines 20-25",
|
|
2433
|
+
"dotnet-legacy/Web/Filters/AuthorizationFilter.cs: no ownership enforcement"
|
|
2434
|
+
],
|
|
2435
|
+
"control_found": null,
|
|
2436
|
+
"notes": "The SaveChanges call persists the bound Customer object without verifying ownership. No compensating control is present.",
|
|
2437
|
+
"suggested_subtype": null,
|
|
2438
|
+
"suggested_cwe": null
|
|
2439
|
+
},
|
|
2440
|
+
"consensus": {
|
|
2441
|
+
"score": 0.95,
|
|
2442
|
+
"agreement_level": "unanimous",
|
|
2443
|
+
"voted_models": [
|
|
2444
|
+
"gpt-oss:20b"
|
|
2445
|
+
],
|
|
2446
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
2447
|
+
},
|
|
2448
|
+
"fingerprint": "kfp1:f8f8312969722c53dc4fbfcebddf09bfc6ea44e77f308058c6f5a0a79916c6a1",
|
|
2449
|
+
"baseline_state": "new"
|
|
2450
|
+
},
|
|
2451
|
+
{
|
|
2452
|
+
"finding_id": "BOLA-R-0012-1",
|
|
2453
|
+
"category": "bola",
|
|
2454
|
+
"subtype": "missing_ownership_check",
|
|
2455
|
+
"title": "BOLA in POST /Customer/Refresh: missing owner verification",
|
|
2456
|
+
"route_id": "R-0012",
|
|
2457
|
+
"endpoint": {
|
|
2458
|
+
"method": "POST",
|
|
2459
|
+
"path": "/Customer/Refresh"
|
|
2460
|
+
},
|
|
2461
|
+
"location": {
|
|
2462
|
+
"file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
|
|
2463
|
+
"start_line": 27
|
|
2464
|
+
},
|
|
2465
|
+
"evidence": [
|
|
2466
|
+
{
|
|
2467
|
+
"file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
|
|
2468
|
+
"start_line": 27,
|
|
2469
|
+
"code": "var customer = db.Customers.Find(id);",
|
|
2470
|
+
"why": "The id comes from the request and is used to load a customer record without checking that the record belongs to the authenticated user.",
|
|
2471
|
+
"line": 27
|
|
2472
|
+
}
|
|
2473
|
+
],
|
|
2474
|
+
"severity": "high",
|
|
2475
|
+
"confidence": "high",
|
|
2476
|
+
"cwe": [
|
|
2477
|
+
"CWE-284",
|
|
2478
|
+
"CWE-639"
|
|
2479
|
+
],
|
|
2480
|
+
"recommendation": "Load the customer and confirm ownership before updating.",
|
|
2481
|
+
"status": "confirmed",
|
|
2482
|
+
"verification": {
|
|
2483
|
+
"verdict": "confirmed",
|
|
2484
|
+
"reason_code": "traced",
|
|
2485
|
+
"checked": [
|
|
2486
|
+
"dotnet-legacy/Web/Controllers/CustomerController.cs: lines 27-30",
|
|
2487
|
+
"dotnet-legacy/Web/Filters/AuthorizationFilter.cs: no ownership enforcement"
|
|
2488
|
+
],
|
|
2489
|
+
"control_found": null,
|
|
2490
|
+
"notes": "The Find call retrieves a Customer by id supplied in the request and is later updated without checking that the record belongs to the current user.",
|
|
2491
|
+
"suggested_subtype": null,
|
|
2492
|
+
"suggested_cwe": null
|
|
2493
|
+
},
|
|
2494
|
+
"consensus": {
|
|
2495
|
+
"score": 0.95,
|
|
2496
|
+
"agreement_level": "unanimous",
|
|
2497
|
+
"voted_models": [
|
|
2498
|
+
"gpt-oss:20b"
|
|
2499
|
+
],
|
|
2500
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
2501
|
+
},
|
|
2502
|
+
"fingerprint": "kfp1:642d9e00b1146b80dc8724396082e7bf839dc74fdcc9224c6452557ed7b19ab7",
|
|
2503
|
+
"baseline_state": "new"
|
|
2504
|
+
},
|
|
2505
|
+
{
|
|
2506
|
+
"finding_id": "BOLA-R-0013-1",
|
|
2507
|
+
"category": "bola",
|
|
2508
|
+
"subtype": "missing_ownership_check",
|
|
2509
|
+
"title": "BOLA in ANY /Invoice/Details: missing owner verification",
|
|
2510
|
+
"route_id": "R-0013",
|
|
2511
|
+
"endpoint": {
|
|
2512
|
+
"method": "ANY",
|
|
2513
|
+
"path": "/Invoice/Details"
|
|
2514
|
+
},
|
|
2515
|
+
"location": {
|
|
2516
|
+
"file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
|
|
2517
|
+
"start_line": 13
|
|
2518
|
+
},
|
|
2519
|
+
"evidence": [
|
|
2520
|
+
{
|
|
2521
|
+
"file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
|
|
2522
|
+
"start_line": 13,
|
|
2523
|
+
"code": "var inv = db.Invoices.Find(id);",
|
|
2524
|
+
"why": "The invoice is retrieved by ID without checking that it belongs to the current user.",
|
|
2525
|
+
"line": 13
|
|
2526
|
+
}
|
|
2527
|
+
],
|
|
2528
|
+
"severity": "high",
|
|
2529
|
+
"confidence": "high",
|
|
2530
|
+
"cwe": [
|
|
2531
|
+
"CWE-639"
|
|
2532
|
+
],
|
|
2533
|
+
"recommendation": "Add a where clause that binds the invoice to the current user.",
|
|
2534
|
+
"status": "confirmed",
|
|
2535
|
+
"verification": {
|
|
2536
|
+
"verdict": "confirmed",
|
|
2537
|
+
"reason_code": "traced",
|
|
2538
|
+
"checked": [
|
|
2539
|
+
"dotnet-legacy/Web/Controllers/InvoiceController.cs line 13",
|
|
2540
|
+
"checked for [Authorize] attributes – present but no ownership check",
|
|
2541
|
+
"checked FilterConfig.cs – no global tenant filter"
|
|
2542
|
+
],
|
|
2543
|
+
"control_found": null,
|
|
2544
|
+
"notes": "The invoice is retrieved by id without verifying that it belongs to the current user. No ownership guard is present.",
|
|
2545
|
+
"suggested_subtype": null,
|
|
2546
|
+
"suggested_cwe": null
|
|
2547
|
+
},
|
|
2548
|
+
"consensus": {
|
|
2549
|
+
"score": 0.95,
|
|
2550
|
+
"agreement_level": "unanimous",
|
|
2551
|
+
"voted_models": [
|
|
2552
|
+
"gpt-oss:20b"
|
|
2553
|
+
],
|
|
2554
|
+
"rationale": "Direct lookup without ownership verification."
|
|
2555
|
+
},
|
|
2556
|
+
"fingerprint": "kfp1:14949287ec6ccdf0c1cfe991e29212347c28b2dcc7f565647cadd54996b5644e",
|
|
2557
|
+
"baseline_state": "new"
|
|
2558
|
+
},
|
|
2559
|
+
{
|
|
2560
|
+
"finding_id": "BOLA-R-0014-1",
|
|
2561
|
+
"category": "bola",
|
|
2562
|
+
"subtype": "missing_ownership_check",
|
|
2563
|
+
"title": "BOLA in ANY /Invoice/Details: missing owner verification",
|
|
2564
|
+
"route_id": "R-0014",
|
|
2565
|
+
"endpoint": {
|
|
2566
|
+
"method": "ANY",
|
|
2567
|
+
"path": "/Invoice/Details"
|
|
2568
|
+
},
|
|
2569
|
+
"location": {
|
|
2570
|
+
"file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
|
|
2571
|
+
"start_line": 13
|
|
2572
|
+
},
|
|
2573
|
+
"evidence": [
|
|
2574
|
+
{
|
|
2575
|
+
"file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
|
|
2576
|
+
"start_line": 13,
|
|
2577
|
+
"code": "var inv = db.Invoices.Find(id);",
|
|
2578
|
+
"why": "The invoice is retrieved by ID without checking that it belongs to the current user.",
|
|
2579
|
+
"line": 13
|
|
2580
|
+
}
|
|
2581
|
+
],
|
|
2582
|
+
"severity": "high",
|
|
2583
|
+
"confidence": "high",
|
|
2584
|
+
"cwe": [
|
|
2585
|
+
"CWE-639"
|
|
2586
|
+
],
|
|
2587
|
+
"recommendation": "Add a where clause that binds the invoice to the current user.",
|
|
2588
|
+
"status": "confirmed",
|
|
2589
|
+
"verification": {
|
|
2590
|
+
"verdict": "confirmed",
|
|
2591
|
+
"reason_code": "traced",
|
|
2592
|
+
"checked": [
|
|
2593
|
+
"dotnet-legacy/Web/Controllers/InvoiceController.cs line 13",
|
|
2594
|
+
"checked for [Authorize] attributes – present but no ownership check",
|
|
2595
|
+
"checked FilterConfig.cs – no global tenant filter"
|
|
2596
|
+
],
|
|
2597
|
+
"control_found": null,
|
|
2598
|
+
"notes": "Same as BOLA-R-0013-1 – any authenticated user can view any invoice.",
|
|
2599
|
+
"suggested_subtype": null,
|
|
2600
|
+
"suggested_cwe": null
|
|
2601
|
+
},
|
|
2602
|
+
"consensus": {
|
|
2603
|
+
"score": 0.95,
|
|
2604
|
+
"agreement_level": "unanimous",
|
|
2605
|
+
"voted_models": [
|
|
2606
|
+
"gpt-oss:20b"
|
|
2607
|
+
],
|
|
2608
|
+
"rationale": "Missing ownership guard."
|
|
2609
|
+
},
|
|
2610
|
+
"fingerprint": "kfp1:5484ee06ac64e82781d6d2d7dabc2df3ee0ec0e85f5902082a7c1de4cf4cca76",
|
|
2611
|
+
"baseline_state": "new"
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
"finding_id": "BOLA-R-0015-1",
|
|
2615
|
+
"category": "bola",
|
|
2616
|
+
"subtype": "missing_ownership_check",
|
|
2617
|
+
"title": "BOLA in POST /Invoice/Search: missing owner verification",
|
|
2618
|
+
"route_id": "R-0015",
|
|
2619
|
+
"endpoint": {
|
|
2620
|
+
"method": "POST",
|
|
2621
|
+
"path": "/Invoice/Search"
|
|
2622
|
+
},
|
|
2623
|
+
"location": {
|
|
2624
|
+
"file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
|
|
2625
|
+
"start_line": 28
|
|
2626
|
+
},
|
|
2627
|
+
"evidence": [
|
|
2628
|
+
{
|
|
2629
|
+
"file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
|
|
2630
|
+
"start_line": 28,
|
|
2631
|
+
"code": "var list = db.Database.SqlQuery<Invoice>(\"SELECT * FROM Invoices WHERE Customer = '\" + customer + \"'\").ToList();",
|
|
2632
|
+
"why": "The query returns invoices for any customer without filtering by the current user.",
|
|
2633
|
+
"line": 28
|
|
2634
|
+
}
|
|
2635
|
+
],
|
|
2636
|
+
"severity": "high",
|
|
2637
|
+
"confidence": "high",
|
|
2638
|
+
"cwe": [
|
|
2639
|
+
"CWE-639"
|
|
2640
|
+
],
|
|
2641
|
+
"recommendation": "Add a where clause that binds the invoices to the current user.",
|
|
2642
|
+
"status": "unverified",
|
|
2643
|
+
"fingerprint": "kfp1:c9f08667bf2ce3a6e3af70cd2f4310ad24eaf0f5493c9502e89b329d742b249c",
|
|
2644
|
+
"baseline_state": "new"
|
|
2645
|
+
},
|
|
2646
|
+
{
|
|
2647
|
+
"finding_id": "BOLA-R-0016-1",
|
|
2648
|
+
"category": "bola",
|
|
2649
|
+
"subtype": "missing_ownership_check",
|
|
2650
|
+
"title": "BOLA in ANY /dotnet-legacy/Web/Handlers/GetFile.ashx: missing owner verification",
|
|
2651
|
+
"route_id": "R-0016",
|
|
2652
|
+
"endpoint": {
|
|
2653
|
+
"method": "ANY",
|
|
2654
|
+
"path": "/dotnet-legacy/Web/Handlers/GetFile.ashx"
|
|
2655
|
+
},
|
|
2656
|
+
"location": {
|
|
2657
|
+
"file": "dotnet-legacy/Web/Handlers/GetFile.ashx",
|
|
2658
|
+
"start_line": 8
|
|
2659
|
+
},
|
|
2660
|
+
"evidence": [
|
|
2661
|
+
{
|
|
2662
|
+
"file": "dotnet-legacy/Web/Handlers/GetFile.ashx",
|
|
2663
|
+
"start_line": 8,
|
|
2664
|
+
"code": "string fileId = context.Request.QueryString[\"file_id\"];",
|
|
2665
|
+
"why": "The file identifier is taken directly from the request and used to locate a file without any check that the file belongs to the authenticated user.",
|
|
2666
|
+
"line": 8
|
|
2667
|
+
}
|
|
2668
|
+
],
|
|
2669
|
+
"severity": "high",
|
|
2670
|
+
"confidence": "high",
|
|
2671
|
+
"cwe": [
|
|
2672
|
+
"CWE-284",
|
|
2673
|
+
"CWE-639"
|
|
2674
|
+
],
|
|
2675
|
+
"recommendation": "Verify that the requested file is owned by the current user before transmitting.",
|
|
2676
|
+
"status": "confirmed",
|
|
2677
|
+
"verification": {
|
|
2678
|
+
"verdict": "confirmed",
|
|
2679
|
+
"reason_code": "traced",
|
|
2680
|
+
"checked": [
|
|
2681
|
+
"dotnet-legacy/Web/Handlers/GetFile.ashx: lines 8-12",
|
|
2682
|
+
"dotnet-legacy/Web/Filters/AuthorizationFilter.cs: no file‑ownership check"
|
|
2683
|
+
],
|
|
2684
|
+
"control_found": null,
|
|
2685
|
+
"notes": "The file_id is taken directly from the query string and used to locate a file without verifying that the file belongs to the authenticated user.",
|
|
2686
|
+
"suggested_subtype": null,
|
|
2687
|
+
"suggested_cwe": null
|
|
2688
|
+
},
|
|
2689
|
+
"consensus": {
|
|
2690
|
+
"score": 0.95,
|
|
2691
|
+
"agreement_level": "unanimous",
|
|
2692
|
+
"voted_models": [
|
|
2693
|
+
"gpt-oss:20b"
|
|
2694
|
+
],
|
|
2695
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
2696
|
+
},
|
|
2697
|
+
"fingerprint": "kfp1:d642a15232edeffab2a15511431577f3f9ce893382573e1c45e203eec7bb0cad",
|
|
2698
|
+
"baseline_state": "new"
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
"finding_id": "BOLA-R-0017-1",
|
|
2702
|
+
"category": "bola",
|
|
2703
|
+
"subtype": "missing_ownership_check",
|
|
2704
|
+
"title": "BOLA in ANY /dotnet-legacy/Web/Orders/EditOrder.aspx: missing owner verification",
|
|
2705
|
+
"route_id": "R-0017",
|
|
2706
|
+
"endpoint": {
|
|
2707
|
+
"method": "ANY",
|
|
2708
|
+
"path": "/dotnet-legacy/Web/Orders/EditOrder.aspx"
|
|
2709
|
+
},
|
|
2710
|
+
"location": {
|
|
2711
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
2712
|
+
"start_line": 7
|
|
2713
|
+
},
|
|
2714
|
+
"evidence": [
|
|
2715
|
+
{
|
|
2716
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
2717
|
+
"start_line": 7,
|
|
2718
|
+
"code": "Dim sql As String = \"UPDATE Orders SET Note = '\" & txtNote.Text & \"'\"",
|
|
2719
|
+
"why": "The UPDATE statement uses the order ID from a hidden field without checking ownership.",
|
|
2720
|
+
"line": 7
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
"file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
|
|
2724
|
+
"start_line": 8,
|
|
2725
|
+
"code": "sql &= \" WHERE OrderId = \" & hfOrderId.Value",
|
|
2726
|
+
"why": "No reference to the current session user.",
|
|
2727
|
+
"line": 8
|
|
2728
|
+
}
|
|
2729
|
+
],
|
|
2730
|
+
"severity": "high",
|
|
2731
|
+
"confidence": "high",
|
|
2732
|
+
"cwe": [
|
|
2733
|
+
"CWE-639"
|
|
2734
|
+
],
|
|
2735
|
+
"recommendation": "Add a WHERE clause that binds the order to the current user.",
|
|
2736
|
+
"status": "unverified",
|
|
2737
|
+
"fingerprint": "kfp1:2ea5e8435292a4d79e53292c88e5a3a1b4a20a146f32ecd3b312dc2abc60a5f2",
|
|
2738
|
+
"baseline_state": "new"
|
|
2739
|
+
},
|
|
2740
|
+
{
|
|
2741
|
+
"finding_id": "BOLA-R-0018-1",
|
|
2742
|
+
"category": "bola",
|
|
2743
|
+
"subtype": "missing_ownership_check",
|
|
2744
|
+
"title": "BOLA in ANY /dotnet-legacy/Web/Orders/OrderDetail.aspx: missing owner verification",
|
|
2745
|
+
"route_id": "R-0018",
|
|
2746
|
+
"endpoint": {
|
|
2747
|
+
"method": "ANY",
|
|
2748
|
+
"path": "/dotnet-legacy/Web/Orders/OrderDetail.aspx"
|
|
2749
|
+
},
|
|
2750
|
+
"location": {
|
|
2751
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
2752
|
+
"start_line": 13
|
|
2753
|
+
},
|
|
2754
|
+
"evidence": [
|
|
2755
|
+
{
|
|
2756
|
+
"file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
|
|
2757
|
+
"start_line": 13,
|
|
2758
|
+
"code": "var cmd = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = \" + id, conn);",
|
|
2759
|
+
"why": "The SELECT statement uses the order ID from the query string without filtering by the current user.",
|
|
2760
|
+
"line": 13
|
|
2761
|
+
}
|
|
2762
|
+
],
|
|
2763
|
+
"severity": "high",
|
|
2764
|
+
"confidence": "high",
|
|
2765
|
+
"cwe": [
|
|
2766
|
+
"CWE-639"
|
|
2767
|
+
],
|
|
2768
|
+
"recommendation": "Add a WHERE clause that binds the order to the current user.",
|
|
2769
|
+
"status": "unverified",
|
|
2770
|
+
"fingerprint": "kfp1:6d5cb3d23c907f7c8c48817288142f24e6de319e80eb0db6d7f3dae90f4271ea",
|
|
2771
|
+
"baseline_state": "new"
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
"finding_id": "BOLA-R-0019-1",
|
|
2775
|
+
"category": "bola",
|
|
2776
|
+
"subtype": "missing_ownership_check",
|
|
2777
|
+
"title": "BOLA in ANY /java-legacy/WebContent/order/detail.jsp: missing owner verification",
|
|
2778
|
+
"route_id": "R-0019",
|
|
2779
|
+
"endpoint": {
|
|
2780
|
+
"method": "ANY",
|
|
2781
|
+
"path": "/java-legacy/WebContent/order/detail.jsp"
|
|
2782
|
+
},
|
|
2783
|
+
"location": {
|
|
2784
|
+
"file": "java-legacy/WebContent/order/detail.jsp",
|
|
2785
|
+
"start_line": 5
|
|
2786
|
+
},
|
|
2787
|
+
"evidence": [
|
|
2788
|
+
{
|
|
2789
|
+
"file": "java-legacy/WebContent/order/detail.jsp",
|
|
2790
|
+
"start_line": 5,
|
|
2791
|
+
"code": "ResultSet rs = c.createStatement().executeQuery(\"SELECT * FROM orders WHERE order_no = '\" + no + \"'\");",
|
|
2792
|
+
"why": "The query returns order data without filtering by the current user.",
|
|
2793
|
+
"line": 5
|
|
2794
|
+
}
|
|
2795
|
+
],
|
|
2796
|
+
"severity": "high",
|
|
2797
|
+
"confidence": "high",
|
|
2798
|
+
"cwe": [
|
|
2799
|
+
"CWE-639"
|
|
2800
|
+
],
|
|
2801
|
+
"recommendation": "Add a WHERE clause that binds the order to the current user.",
|
|
2802
|
+
"status": "unverified",
|
|
2803
|
+
"fingerprint": "kfp1:9cecb903db7233388235fac7fff1a12f16eb180dc75e23ae1867fa02be7a40eb",
|
|
2804
|
+
"baseline_state": "new"
|
|
2805
|
+
},
|
|
2806
|
+
{
|
|
2807
|
+
"finding_id": "BOLA-R-0020",
|
|
2808
|
+
"category": "bola",
|
|
2809
|
+
"subtype": "missing_ownership_check",
|
|
2810
|
+
"title": "BOLA in POST /account/save: missing owner verification",
|
|
2811
|
+
"route_id": "R-0020",
|
|
2812
|
+
"endpoint": {
|
|
2813
|
+
"method": "POST",
|
|
2814
|
+
"path": "/account/save"
|
|
2815
|
+
},
|
|
2816
|
+
"location": {
|
|
2817
|
+
"file": "java-legacy/src/com/acme/web/AccountController.java",
|
|
2818
|
+
"start_line": 15
|
|
2819
|
+
},
|
|
2820
|
+
"evidence": [
|
|
2821
|
+
{
|
|
2822
|
+
"file": "java-legacy/src/com/acme/web/AccountController.java",
|
|
2823
|
+
"start_line": 15,
|
|
2824
|
+
"code": "accountDao.save(account);",
|
|
2825
|
+
"why": "The account object is persisted without checking that its owner matches the current session user, allowing any authenticated user to overwrite another user’s account data.",
|
|
2826
|
+
"line": 15
|
|
2827
|
+
}
|
|
2828
|
+
],
|
|
2829
|
+
"severity": "high",
|
|
2830
|
+
"confidence": "high",
|
|
2831
|
+
"cwe": [
|
|
2832
|
+
"CWE-284",
|
|
2833
|
+
"CWE-639"
|
|
2834
|
+
],
|
|
2835
|
+
"recommendation": "Bind the save operation to the current session user or validate ownership before persisting.",
|
|
2836
|
+
"status": "unverified",
|
|
2837
|
+
"fingerprint": "kfp1:24fe855b446d28a5d00c674816e053358f40cdb1d4848028f603352d60fda884",
|
|
2838
|
+
"baseline_state": "new"
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
"finding_id": "BOLA-R-0021",
|
|
2842
|
+
"category": "bola",
|
|
2843
|
+
"subtype": "missing_ownership_check",
|
|
2844
|
+
"title": "BOLA in POST /account/rename: missing owner verification",
|
|
2845
|
+
"route_id": "R-0021",
|
|
2846
|
+
"endpoint": {
|
|
2847
|
+
"method": "POST",
|
|
2848
|
+
"path": "/account/rename"
|
|
2849
|
+
},
|
|
2850
|
+
"location": {
|
|
2851
|
+
"file": "java-legacy/src/com/acme/web/AccountController.java",
|
|
2852
|
+
"start_line": 21
|
|
2853
|
+
},
|
|
2854
|
+
"evidence": [
|
|
2855
|
+
{
|
|
2856
|
+
"file": "java-legacy/src/com/acme/web/AccountController.java",
|
|
2857
|
+
"start_line": 21,
|
|
2858
|
+
"code": "accountDao.rename(form.getId(), form.getName());",
|
|
2859
|
+
"why": "The rename operation accepts an arbitrary account ID from the request and updates it without verifying that the ID belongs to the current user.",
|
|
2860
|
+
"line": 21
|
|
2861
|
+
}
|
|
2862
|
+
],
|
|
2863
|
+
"severity": "high",
|
|
2864
|
+
"confidence": "high",
|
|
2865
|
+
"cwe": [
|
|
2866
|
+
"CWE-284",
|
|
2867
|
+
"CWE-639"
|
|
2868
|
+
],
|
|
2869
|
+
"recommendation": "Verify that the account ID belongs to the current session user before renaming.",
|
|
2870
|
+
"status": "unverified",
|
|
2871
|
+
"fingerprint": "kfp1:9861ff13d6d3a7c7b166ad5640320a1c58d0a7e89965d79a86f14deddaeae0af",
|
|
2872
|
+
"baseline_state": "new"
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
"finding_id": "BOLA-R-0022",
|
|
2876
|
+
"category": "bola",
|
|
2877
|
+
"subtype": "missing_ownership_check",
|
|
2878
|
+
"title": "BOLA in PUT /account/api: missing owner verification",
|
|
2879
|
+
"route_id": "R-0022",
|
|
2880
|
+
"endpoint": {
|
|
2881
|
+
"method": "PUT",
|
|
2882
|
+
"path": "/account/api"
|
|
2883
|
+
},
|
|
2884
|
+
"location": {
|
|
2885
|
+
"file": "java-legacy/src/com/acme/web/AccountController.java",
|
|
2886
|
+
"start_line": 28
|
|
2887
|
+
},
|
|
2888
|
+
"evidence": [
|
|
2889
|
+
{
|
|
2890
|
+
"file": "java-legacy/src/com/acme/web/AccountController.java",
|
|
2891
|
+
"start_line": 28,
|
|
2892
|
+
"code": "return accountDao.save(account);",
|
|
2893
|
+
"why": "The API accepts a full Account object and persists it without checking that the account belongs to the current user.",
|
|
2894
|
+
"line": 28
|
|
2895
|
+
}
|
|
2896
|
+
],
|
|
2897
|
+
"severity": "high",
|
|
2898
|
+
"confidence": "high",
|
|
2899
|
+
"cwe": [
|
|
2900
|
+
"CWE-284",
|
|
2901
|
+
"CWE-639"
|
|
2902
|
+
],
|
|
2903
|
+
"recommendation": "Enforce ownership on the DAO layer or validate before calling save.",
|
|
2904
|
+
"status": "unverified",
|
|
2905
|
+
"fingerprint": "kfp1:dd0493f7dcb3e1d57330e174623390900a4d5a957be4da90e8e5de642479da69",
|
|
2906
|
+
"baseline_state": "new"
|
|
2907
|
+
},
|
|
2908
|
+
{
|
|
2909
|
+
"finding_id": "BOLA-R-0023",
|
|
2910
|
+
"category": "bola",
|
|
2911
|
+
"subtype": "missing_role_check",
|
|
2912
|
+
"title": "BOLA in POST /admin/users/{id}/delete: missing role guard",
|
|
2913
|
+
"route_id": "R-0023",
|
|
2914
|
+
"endpoint": {
|
|
2915
|
+
"method": "POST",
|
|
2916
|
+
"path": "/admin/users/{id}/delete"
|
|
2917
|
+
},
|
|
2918
|
+
"location": {
|
|
2919
|
+
"file": "java-legacy/src/com/acme/web/AdminUserController.java",
|
|
2920
|
+
"start_line": 15
|
|
2921
|
+
},
|
|
2922
|
+
"evidence": [
|
|
2923
|
+
{
|
|
2924
|
+
"file": "java-legacy/src/com/acme/web/AdminUserController.java",
|
|
2925
|
+
"start_line": 15,
|
|
2926
|
+
"code": "userDao.deleteById(id);",
|
|
2927
|
+
"why": "Any authenticated user can delete any user because there is no role or ownership check guarding the delete operation.",
|
|
2928
|
+
"line": 15
|
|
2929
|
+
}
|
|
2930
|
+
],
|
|
2931
|
+
"severity": "high",
|
|
2932
|
+
"confidence": "high",
|
|
2933
|
+
"cwe": [
|
|
2934
|
+
"CWE-284",
|
|
2935
|
+
"CWE-639"
|
|
2936
|
+
],
|
|
2937
|
+
"recommendation": "Add an admin role guard and optionally verify ownership if required.",
|
|
2938
|
+
"status": "unverified",
|
|
2939
|
+
"fingerprint": "kfp1:97b5388d6d69694bad123ae010882ff94355d581f66a44c465a2563b00641b68",
|
|
2940
|
+
"baseline_state": "new"
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
"finding_id": "BOLA-R-0025",
|
|
2944
|
+
"category": "bola",
|
|
2945
|
+
"subtype": "missing_ownership_check",
|
|
2946
|
+
"title": "BOLA in GET /invoices/{id}: missing owner verification",
|
|
2947
|
+
"route_id": "R-0025",
|
|
2948
|
+
"endpoint": {
|
|
2949
|
+
"method": "GET",
|
|
2950
|
+
"path": "/invoices/{id}"
|
|
2951
|
+
},
|
|
2952
|
+
"location": {
|
|
2953
|
+
"file": "java-legacy/src/com/acme/web/InvoiceController.java",
|
|
2954
|
+
"start_line": 17
|
|
2955
|
+
},
|
|
2956
|
+
"evidence": [
|
|
2957
|
+
{
|
|
2958
|
+
"file": "java-legacy/src/com/acme/web/InvoiceController.java",
|
|
2959
|
+
"start_line": 17,
|
|
2960
|
+
"code": "model.addAttribute(\"invoice\", invoiceDao.findById(id));",
|
|
2961
|
+
"why": "The invoice is fetched by ID without checking that the current user owns it, allowing any authenticated user to view any invoice.",
|
|
2962
|
+
"line": 17
|
|
2963
|
+
}
|
|
2964
|
+
],
|
|
2965
|
+
"severity": "high",
|
|
2966
|
+
"confidence": "high",
|
|
2967
|
+
"cwe": [
|
|
2968
|
+
"CWE-284",
|
|
2969
|
+
"CWE-639"
|
|
2970
|
+
],
|
|
2971
|
+
"recommendation": "Bind the query to the current user or validate ownership before returning the invoice.",
|
|
2972
|
+
"status": "unverified",
|
|
2973
|
+
"fingerprint": "kfp1:5d584a61310d261639c30a41d5c01a1591be415823c1431b9ed0cdd4e6d1549f",
|
|
2974
|
+
"baseline_state": "new"
|
|
2975
|
+
},
|
|
2976
|
+
{
|
|
2977
|
+
"finding_id": "BOLA-R-0027",
|
|
2978
|
+
"category": "bola",
|
|
2979
|
+
"subtype": "missing_ownership_check",
|
|
2980
|
+
"title": "BOLA in GET /invoices/search: missing auth and owner filtering",
|
|
2981
|
+
"route_id": "R-0027",
|
|
2982
|
+
"endpoint": {
|
|
2983
|
+
"method": "GET",
|
|
2984
|
+
"path": "/invoices/search"
|
|
2985
|
+
},
|
|
2986
|
+
"location": {
|
|
2987
|
+
"file": "java-legacy/src/com/acme/web/InvoiceController.java",
|
|
2988
|
+
"start_line": 29
|
|
2989
|
+
},
|
|
2990
|
+
"evidence": [
|
|
2991
|
+
{
|
|
2992
|
+
"file": "java-legacy/src/com/acme/web/InvoiceController.java",
|
|
2993
|
+
"start_line": 29,
|
|
2994
|
+
"code": "session.createQuery(\"from Invoice i where i.customer = '\" + customer + \"'\").list();",
|
|
2995
|
+
"why": "The query is built from a request parameter and returns all invoices for the given customer without restricting to the current user or checking authentication.",
|
|
2996
|
+
"line": 29
|
|
2997
|
+
}
|
|
2998
|
+
],
|
|
2999
|
+
"severity": "high",
|
|
3000
|
+
"confidence": "high",
|
|
3001
|
+
"cwe": [
|
|
3002
|
+
"CWE-284",
|
|
3003
|
+
"CWE-639"
|
|
3004
|
+
],
|
|
3005
|
+
"recommendation": "Require authentication and filter results by the current user’s tenant or owner.",
|
|
3006
|
+
"status": "confirmed",
|
|
3007
|
+
"verification": {
|
|
3008
|
+
"verdict": "confirmed",
|
|
3009
|
+
"reason_code": "traced",
|
|
3010
|
+
"checked": [
|
|
3011
|
+
"java-legacy/src/com/acme/web/InvoiceController.java",
|
|
3012
|
+
"java-legacy/web.xml"
|
|
3013
|
+
],
|
|
3014
|
+
"control_found": null,
|
|
3015
|
+
"notes": "The controller builds a query directly from the request parameter 'customer' without any authentication check or owner filtering. No global filter or interceptor enforces authentication for this endpoint, and the query is executed immediately. No integer cast or allow‑list is applied to the parameter.",
|
|
3016
|
+
"suggested_subtype": null,
|
|
3017
|
+
"suggested_cwe": null
|
|
3018
|
+
},
|
|
3019
|
+
"consensus": {
|
|
3020
|
+
"score": 0.95,
|
|
3021
|
+
"agreement_level": "unanimous",
|
|
3022
|
+
"voted_models": [
|
|
3023
|
+
"gpt-oss:20b"
|
|
3024
|
+
],
|
|
3025
|
+
"rationale": "No compensating control found; the query is vulnerable to BOLA."
|
|
3026
|
+
},
|
|
3027
|
+
"fingerprint": "kfp1:1cf3d8c24a4b1ec82a1a260ddafcba873b05ec821d2005d7910795b92d8a5586",
|
|
3028
|
+
"baseline_state": "new"
|
|
3029
|
+
},
|
|
3030
|
+
{
|
|
3031
|
+
"finding_id": "BOLA-R-0028",
|
|
3032
|
+
"category": "bola",
|
|
3033
|
+
"subtype": "missing_ownership_check",
|
|
3034
|
+
"title": "BOLA in GET /order/view: missing auth and owner filtering",
|
|
3035
|
+
"route_id": "R-0028",
|
|
3036
|
+
"endpoint": {
|
|
3037
|
+
"method": "GET",
|
|
3038
|
+
"path": "/order/view"
|
|
3039
|
+
},
|
|
3040
|
+
"location": {
|
|
3041
|
+
"file": "java-legacy/src/com/acme/web/OrderServlet.java",
|
|
3042
|
+
"start_line": 11
|
|
3043
|
+
},
|
|
3044
|
+
"evidence": [
|
|
3045
|
+
{
|
|
3046
|
+
"file": "java-legacy/src/com/acme/web/OrderServlet.java",
|
|
3047
|
+
"start_line": 11,
|
|
3048
|
+
"code": "String id = request.getParameter(\"orderId\");",
|
|
3049
|
+
"why": "The order ID is taken directly from the request.",
|
|
3050
|
+
"line": 11
|
|
3051
|
+
},
|
|
3052
|
+
{
|
|
3053
|
+
"file": "java-legacy/src/com/acme/web/OrderServlet.java",
|
|
3054
|
+
"start_line": 15,
|
|
3055
|
+
"code": "ResultSet rs = stmt.executeQuery(\"SELECT * FROM orders WHERE id = '\" + id + \"'\");",
|
|
3056
|
+
"why": "The query retrieves the order without filtering by the current user’s ID, allowing any authenticated user to view any order.",
|
|
3057
|
+
"line": 15
|
|
3058
|
+
}
|
|
3059
|
+
],
|
|
3060
|
+
"severity": "high",
|
|
3061
|
+
"confidence": "high",
|
|
3062
|
+
"cwe": [
|
|
3063
|
+
"CWE-284",
|
|
3064
|
+
"CWE-639"
|
|
3065
|
+
],
|
|
3066
|
+
"recommendation": "Add a check that the order belongs to the current session user before returning data.",
|
|
3067
|
+
"status": "confirmed",
|
|
3068
|
+
"verification": {
|
|
3069
|
+
"verdict": "confirmed",
|
|
3070
|
+
"reason_code": "traced",
|
|
3071
|
+
"checked": [
|
|
3072
|
+
"java-legacy/src/com/acme/web/OrderServlet.java",
|
|
3073
|
+
"java-legacy/web.xml"
|
|
3074
|
+
],
|
|
3075
|
+
"control_found": null,
|
|
3076
|
+
"notes": "OrderServlet reads the orderId from the request and uses it directly in a SQL query without authentication or ownership checks. No global filter or interceptor protects this endpoint, and the query is executed immediately.",
|
|
3077
|
+
"suggested_subtype": null,
|
|
3078
|
+
"suggested_cwe": null
|
|
3079
|
+
},
|
|
3080
|
+
"consensus": {
|
|
3081
|
+
"score": 0.95,
|
|
3082
|
+
"agreement_level": "unanimous",
|
|
3083
|
+
"voted_models": [
|
|
3084
|
+
"gpt-oss:20b"
|
|
3085
|
+
],
|
|
3086
|
+
"rationale": "No compensating control found; the query is vulnerable to BOLA."
|
|
3087
|
+
},
|
|
3088
|
+
"fingerprint": "kfp1:a589b3cdcfea2b9cc2c5190ffe4a0c2989e46fdd4d15b59781c1b74e3c554524",
|
|
3089
|
+
"baseline_state": "new"
|
|
3090
|
+
},
|
|
3091
|
+
{
|
|
3092
|
+
"finding_id": "BOLA-R-0029-1",
|
|
3093
|
+
"category": "bola",
|
|
3094
|
+
"subtype": "missing_ownership_check",
|
|
3095
|
+
"title": "BOLA in POST <servlet-mapping>: missing owner verification",
|
|
3096
|
+
"route_id": "R-0029",
|
|
3097
|
+
"endpoint": {
|
|
3098
|
+
"method": "POST",
|
|
3099
|
+
"path": "<servlet-mapping>"
|
|
3100
|
+
},
|
|
3101
|
+
"location": {
|
|
3102
|
+
"file": "java-legacy/src/com/acme/web/ProfileServlet.java",
|
|
3103
|
+
"start_line": 12
|
|
3104
|
+
},
|
|
3105
|
+
"evidence": [
|
|
3106
|
+
{
|
|
3107
|
+
"file": "java-legacy/src/com/acme/web/ProfileServlet.java",
|
|
3108
|
+
"start_line": 12,
|
|
3109
|
+
"code": "BeanUtils.populate(account, request.getParameterMap());",
|
|
3110
|
+
"why": "The Account object is populated from request parameters and can contain an Id that is used to modify any account without verifying ownership.",
|
|
3111
|
+
"line": 12
|
|
3112
|
+
}
|
|
3113
|
+
],
|
|
3114
|
+
"severity": "high",
|
|
3115
|
+
"confidence": "high",
|
|
3116
|
+
"cwe": [
|
|
3117
|
+
"CWE-284",
|
|
3118
|
+
"CWE-639"
|
|
3119
|
+
],
|
|
3120
|
+
"recommendation": "Load the account by Id and confirm that it belongs to the authenticated user before applying changes.",
|
|
3121
|
+
"status": "confirmed",
|
|
3122
|
+
"verification": {
|
|
3123
|
+
"verdict": "confirmed",
|
|
3124
|
+
"reason_code": "traced",
|
|
3125
|
+
"checked": [
|
|
3126
|
+
"java-legacy/src/com/acme/web/ProfileServlet.java: lines 12-15",
|
|
3127
|
+
"java-legacy/src/com/acme/web/filters/AuthorizationFilter.java: no ownership enforcement"
|
|
3128
|
+
],
|
|
3129
|
+
"control_found": null,
|
|
3130
|
+
"notes": "BeanUtils populates an Account object from request parameters, including an id that can be used to modify any account. No check ensures the id belongs to the current session user.",
|
|
3131
|
+
"suggested_subtype": null,
|
|
3132
|
+
"suggested_cwe": null
|
|
3133
|
+
},
|
|
3134
|
+
"consensus": {
|
|
3135
|
+
"score": 0.95,
|
|
3136
|
+
"agreement_level": "unanimous",
|
|
3137
|
+
"voted_models": [
|
|
3138
|
+
"gpt-oss:20b"
|
|
3139
|
+
],
|
|
3140
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
3141
|
+
},
|
|
3142
|
+
"fingerprint": "kfp1:17cf1d28cd80a9968049c1e0d847f71392aac7a18432c0c8bc2eb583687f3a31",
|
|
3143
|
+
"baseline_state": "new"
|
|
3144
|
+
},
|
|
3145
|
+
{
|
|
3146
|
+
"finding_id": "BOLA-R-0030",
|
|
3147
|
+
"category": "bola",
|
|
3148
|
+
"subtype": "missing_role_check",
|
|
3149
|
+
"title": "BOLA in POST /admin/users/:id/delete: missing role guard",
|
|
3150
|
+
"route_id": "R-0030",
|
|
3151
|
+
"endpoint": {
|
|
3152
|
+
"method": "POST",
|
|
3153
|
+
"path": "/admin/users/:id/delete"
|
|
3154
|
+
},
|
|
3155
|
+
"location": {
|
|
3156
|
+
"file": "node-express/routes/admin.js",
|
|
3157
|
+
"start_line": 9
|
|
3158
|
+
},
|
|
3159
|
+
"evidence": [
|
|
3160
|
+
{
|
|
3161
|
+
"file": "node-express/routes/admin.js",
|
|
3162
|
+
"start_line": 9,
|
|
3163
|
+
"code": "User.deleteOne({ _id: req.params.id }, function () { res.sendStatus(204); });",
|
|
3164
|
+
"why": "Any logged‑in user can delete any user because the route only checks that req.user exists, not that the user is an admin.",
|
|
3165
|
+
"line": 9
|
|
3166
|
+
}
|
|
3167
|
+
],
|
|
3168
|
+
"severity": "high",
|
|
3169
|
+
"confidence": "high",
|
|
3170
|
+
"cwe": [
|
|
3171
|
+
"CWE-284",
|
|
3172
|
+
"CWE-639"
|
|
3173
|
+
],
|
|
3174
|
+
"recommendation": "Add an admin role guard before performing the delete.",
|
|
3175
|
+
"status": "confirmed",
|
|
3176
|
+
"verification": {
|
|
3177
|
+
"verdict": "confirmed",
|
|
3178
|
+
"reason_code": "traced",
|
|
3179
|
+
"checked": [
|
|
3180
|
+
"node-express/routes/admin.js",
|
|
3181
|
+
"node-express/app.js"
|
|
3182
|
+
],
|
|
3183
|
+
"control_found": null,
|
|
3184
|
+
"notes": "The route deletes a user by id without checking that the requester has an admin role. No middleware or guard enforces an admin role for this endpoint; only req.user is checked, which is insufficient.",
|
|
3185
|
+
"suggested_subtype": null,
|
|
3186
|
+
"suggested_cwe": null
|
|
3187
|
+
},
|
|
3188
|
+
"consensus": {
|
|
3189
|
+
"score": 0.95,
|
|
3190
|
+
"agreement_level": "unanimous",
|
|
3191
|
+
"voted_models": [
|
|
3192
|
+
"gpt-oss:20b"
|
|
3193
|
+
],
|
|
3194
|
+
"rationale": "No role guard found; the endpoint is vulnerable to BOLA."
|
|
3195
|
+
},
|
|
3196
|
+
"fingerprint": "kfp1:df4de37e93a17deec9a804933767d93f194c63dc01fd9cdffb6b334ff23a93a5",
|
|
3197
|
+
"baseline_state": "new"
|
|
3198
|
+
},
|
|
3199
|
+
{
|
|
3200
|
+
"finding_id": "BOLA-R-0033-1",
|
|
3201
|
+
"category": "bola",
|
|
3202
|
+
"subtype": "missing_ownership_check",
|
|
3203
|
+
"title": "BOLA in GET /orders/:id: missing owner verification",
|
|
3204
|
+
"route_id": "R-0033",
|
|
3205
|
+
"endpoint": {
|
|
3206
|
+
"method": "GET",
|
|
3207
|
+
"path": "/orders/:id"
|
|
3208
|
+
},
|
|
3209
|
+
"location": {
|
|
3210
|
+
"file": "node-express/routes/orders.js",
|
|
3211
|
+
"start_line": 8
|
|
3212
|
+
},
|
|
3213
|
+
"evidence": [
|
|
3214
|
+
{
|
|
3215
|
+
"file": "node-express/routes/orders.js",
|
|
3216
|
+
"start_line": 8,
|
|
3217
|
+
"code": "Order.findById(req.params.id, function (err, order) { res.json(order); });",
|
|
3218
|
+
"why": "The order ID comes from the request and the query does not constrain the result to the authenticated user.",
|
|
3219
|
+
"line": 8
|
|
3220
|
+
}
|
|
3221
|
+
],
|
|
3222
|
+
"severity": "high",
|
|
3223
|
+
"confidence": "high",
|
|
3224
|
+
"cwe": [
|
|
3225
|
+
"CWE-284",
|
|
3226
|
+
"CWE-639"
|
|
3227
|
+
],
|
|
3228
|
+
"recommendation": "Bind the query to the current session user.",
|
|
3229
|
+
"status": "confirmed",
|
|
3230
|
+
"verification": {
|
|
3231
|
+
"verdict": "confirmed",
|
|
3232
|
+
"reason_code": "traced",
|
|
3233
|
+
"checked": [
|
|
3234
|
+
"node-express/routes/orders.js",
|
|
3235
|
+
"node-express/app.js"
|
|
3236
|
+
],
|
|
3237
|
+
"control_found": null,
|
|
3238
|
+
"notes": "Order.findById is called with the id from the request and the result is returned without any ownership check. No middleware enforces that the order belongs to the authenticated user.",
|
|
3239
|
+
"suggested_subtype": null,
|
|
3240
|
+
"suggested_cwe": null
|
|
3241
|
+
},
|
|
3242
|
+
"consensus": {
|
|
3243
|
+
"score": 0.95,
|
|
3244
|
+
"agreement_level": "unanimous",
|
|
3245
|
+
"voted_models": [
|
|
3246
|
+
"gpt-oss:20b"
|
|
3247
|
+
],
|
|
3248
|
+
"rationale": "No compensating control found; the endpoint is vulnerable to BOLA."
|
|
3249
|
+
},
|
|
3250
|
+
"fingerprint": "kfp1:3efb41972fd6c818041a922563d6a1e65eb9cdcd63b764b52ff4ffce238a6c8d",
|
|
3251
|
+
"baseline_state": "new"
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
"finding_id": "BOLA-R-0043-1",
|
|
3255
|
+
"category": "bola",
|
|
3256
|
+
"subtype": "missing_ownership_check",
|
|
3257
|
+
"title": "BOLA in Perl CGI order.cgi: missing owner verification",
|
|
3258
|
+
"route_id": "R-0043",
|
|
3259
|
+
"endpoint": {
|
|
3260
|
+
"method": "ANY",
|
|
3261
|
+
"path": "/perl-cgi/cgi-bin/order.cgi"
|
|
3262
|
+
},
|
|
3263
|
+
"location": {
|
|
3264
|
+
"file": "perl-cgi/cgi-bin/order.cgi",
|
|
3265
|
+
"start_line": 13
|
|
3266
|
+
},
|
|
3267
|
+
"evidence": [
|
|
3268
|
+
{
|
|
3269
|
+
"file": "perl-cgi/cgi-bin/order.cgi",
|
|
3270
|
+
"start_line": 13,
|
|
3271
|
+
"code": "$sth = $dbh->prepare(\"SELECT * FROM orders WHERE id = $id\");",
|
|
3272
|
+
"why": "The order ID is taken directly from the request and the query does not filter by the authenticated user.",
|
|
3273
|
+
"line": 13
|
|
3274
|
+
}
|
|
3275
|
+
],
|
|
3276
|
+
"severity": "high",
|
|
3277
|
+
"confidence": "high",
|
|
3278
|
+
"cwe": [
|
|
3279
|
+
"CWE-284",
|
|
3280
|
+
"CWE-639"
|
|
3281
|
+
],
|
|
3282
|
+
"recommendation": "Add a user_id filter to the query.",
|
|
3283
|
+
"status": "confirmed",
|
|
3284
|
+
"verification": {
|
|
3285
|
+
"verdict": "confirmed",
|
|
3286
|
+
"reason_code": "traced",
|
|
3287
|
+
"checked": [
|
|
3288
|
+
"perl-cgi/cgi-bin/order.cgi",
|
|
3289
|
+
"perl-cgi/cgi-bin/.htaccess"
|
|
3290
|
+
],
|
|
3291
|
+
"control_found": null,
|
|
3292
|
+
"notes": "The CGI script uses the id from the request directly in a SQL query without any authentication or ownership filtering. No .htaccess or CGI wrapper enforces authentication for this script.",
|
|
3293
|
+
"suggested_subtype": null,
|
|
3294
|
+
"suggested_cwe": null
|
|
3295
|
+
},
|
|
3296
|
+
"consensus": {
|
|
3297
|
+
"score": 0.95,
|
|
3298
|
+
"agreement_level": "unanimous",
|
|
3299
|
+
"voted_models": [
|
|
3300
|
+
"gpt-oss:20b"
|
|
3301
|
+
],
|
|
3302
|
+
"rationale": "No compensating control found; the endpoint is vulnerable to BOLA."
|
|
3303
|
+
},
|
|
3304
|
+
"fingerprint": "kfp1:0823c809d4892eb324f1b12ff01c568fed5e7b10e716e2a0064b9ed53c6f0380",
|
|
3305
|
+
"baseline_state": "new"
|
|
3306
|
+
},
|
|
3307
|
+
{
|
|
3308
|
+
"finding_id": "BOLA-R-0044-1",
|
|
3309
|
+
"category": "bola",
|
|
3310
|
+
"subtype": "missing_ownership_check",
|
|
3311
|
+
"title": "BOLA in PHP CodeIgniter Invoice view: missing owner verification",
|
|
3312
|
+
"route_id": "R-0044",
|
|
3313
|
+
"endpoint": {
|
|
3314
|
+
"method": "ANY",
|
|
3315
|
+
"path": "/invoice/view/{id}"
|
|
3316
|
+
},
|
|
3317
|
+
"location": {
|
|
3318
|
+
"file": "php-codeigniter/application/controllers/Invoice.php",
|
|
3319
|
+
"start_line": 10
|
|
3320
|
+
},
|
|
3321
|
+
"evidence": [
|
|
3322
|
+
{
|
|
3323
|
+
"file": "php-codeigniter/application/controllers/Invoice.php",
|
|
3324
|
+
"start_line": 10,
|
|
3325
|
+
"code": "$q = $this->db->query(\"SELECT * FROM invoices WHERE id = $id\");",
|
|
3326
|
+
"why": "The invoice ID is taken from the request and the query does not constrain the result to the authenticated user.",
|
|
3327
|
+
"line": 10
|
|
3328
|
+
}
|
|
3329
|
+
],
|
|
3330
|
+
"severity": "high",
|
|
3331
|
+
"confidence": "high",
|
|
3332
|
+
"cwe": [
|
|
3333
|
+
"CWE-284",
|
|
3334
|
+
"CWE-639"
|
|
3335
|
+
],
|
|
3336
|
+
"recommendation": "Add a user_id filter to the query.",
|
|
3337
|
+
"status": "confirmed",
|
|
3338
|
+
"verification": {
|
|
3339
|
+
"verdict": "confirmed",
|
|
3340
|
+
"reason_code": "traced",
|
|
3341
|
+
"checked": [
|
|
3342
|
+
"php-codeigniter/application/controllers/Invoice.php",
|
|
3343
|
+
"php-codeigniter/application/config/autoload.php"
|
|
3344
|
+
],
|
|
3345
|
+
"control_found": null,
|
|
3346
|
+
"notes": "The controller runs a raw query with the id from the request and returns the result without filtering by the logged‑in user. No CodeIgniter hook or controller constructor enforces authentication or ownership.",
|
|
3347
|
+
"suggested_subtype": null,
|
|
3348
|
+
"suggested_cwe": null
|
|
3349
|
+
},
|
|
3350
|
+
"consensus": {
|
|
3351
|
+
"score": 0.95,
|
|
3352
|
+
"agreement_level": "unanimous",
|
|
3353
|
+
"voted_models": [
|
|
3354
|
+
"gpt-oss:20b"
|
|
3355
|
+
],
|
|
3356
|
+
"rationale": "No compensating control found; the endpoint is vulnerable to BOLA."
|
|
3357
|
+
},
|
|
3358
|
+
"fingerprint": "kfp1:9e7c8370d77a2f7c2d568a97ad128c427ef21f3b9775cfaa95eb72b2c5da6115",
|
|
3359
|
+
"baseline_state": "new"
|
|
3360
|
+
},
|
|
3361
|
+
{
|
|
3362
|
+
"finding_id": "BOLA-R-0046-1",
|
|
3363
|
+
"category": "bola",
|
|
3364
|
+
"subtype": "missing_ownership_check",
|
|
3365
|
+
"title": "BOLA in PHP CodeIgniter Invoice update: missing owner verification",
|
|
3366
|
+
"route_id": "R-0046",
|
|
3367
|
+
"endpoint": {
|
|
3368
|
+
"method": "ANY",
|
|
3369
|
+
"path": "/invoice/update"
|
|
3370
|
+
},
|
|
3371
|
+
"location": {
|
|
3372
|
+
"file": "php-codeigniter/application/controllers/Invoice.php",
|
|
3373
|
+
"start_line": 23
|
|
3374
|
+
},
|
|
3375
|
+
"evidence": [
|
|
3376
|
+
{
|
|
3377
|
+
"file": "php-codeigniter/application/controllers/Invoice.php",
|
|
3378
|
+
"start_line": 23,
|
|
3379
|
+
"code": "$this->db->query(\"UPDATE invoices SET note = ? WHERE id = ?\", array($note, $id));",
|
|
3380
|
+
"why": "The invoice ID is taken from the request and the update does not verify that the invoice belongs to the authenticated user.",
|
|
3381
|
+
"line": 23
|
|
3382
|
+
}
|
|
3383
|
+
],
|
|
3384
|
+
"severity": "high",
|
|
3385
|
+
"confidence": "high",
|
|
3386
|
+
"cwe": [
|
|
3387
|
+
"CWE-284",
|
|
3388
|
+
"CWE-639"
|
|
3389
|
+
],
|
|
3390
|
+
"recommendation": "Add a user_id filter to the update query.",
|
|
3391
|
+
"status": "confirmed",
|
|
3392
|
+
"verification": {
|
|
3393
|
+
"verdict": "confirmed",
|
|
3394
|
+
"reason_code": "traced",
|
|
3395
|
+
"checked": [
|
|
3396
|
+
"php-codeigniter/application/controllers/Invoice.php",
|
|
3397
|
+
"php-codeigniter/application/config/autoload.php"
|
|
3398
|
+
],
|
|
3399
|
+
"control_found": null,
|
|
3400
|
+
"notes": "The update query uses the id from the request without verifying that the invoice belongs to the authenticated user. No ownership check or authentication guard is present.",
|
|
3401
|
+
"suggested_subtype": null,
|
|
3402
|
+
"suggested_cwe": null
|
|
3403
|
+
},
|
|
3404
|
+
"consensus": {
|
|
3405
|
+
"score": 0.95,
|
|
3406
|
+
"agreement_level": "unanimous",
|
|
3407
|
+
"voted_models": [
|
|
3408
|
+
"gpt-oss:20b"
|
|
3409
|
+
],
|
|
3410
|
+
"rationale": "No compensating control found; the endpoint is vulnerable to BOLA."
|
|
3411
|
+
},
|
|
3412
|
+
"fingerprint": "kfp1:4f200be5155b2854ef32764906134098549058ea95b61c478a18aa59988ef8cb",
|
|
3413
|
+
"baseline_state": "new"
|
|
3414
|
+
},
|
|
3415
|
+
{
|
|
3416
|
+
"finding_id": "BOLA-R-0052-1",
|
|
3417
|
+
"category": "bola",
|
|
3418
|
+
"subtype": "no_authentication",
|
|
3419
|
+
"title": "No authentication on POST /register",
|
|
3420
|
+
"route_id": "R-0052",
|
|
3421
|
+
"endpoint": {
|
|
3422
|
+
"method": "POST",
|
|
3423
|
+
"path": "/register"
|
|
3424
|
+
},
|
|
3425
|
+
"location": {
|
|
3426
|
+
"file": "php-laravel/routes/web.php",
|
|
3427
|
+
"start_line": 6
|
|
3428
|
+
},
|
|
3429
|
+
"evidence": [
|
|
3430
|
+
{
|
|
3431
|
+
"file": "php-laravel/routes/web.php",
|
|
3432
|
+
"start_line": 6,
|
|
3433
|
+
"code": "Route::post('/register', [AccountController::class, 'register']);",
|
|
3434
|
+
"why": "Route is defined without auth middleware",
|
|
3435
|
+
"line": 6
|
|
3436
|
+
}
|
|
3437
|
+
],
|
|
3438
|
+
"severity": "high",
|
|
3439
|
+
"confidence": "high",
|
|
3440
|
+
"cwe": [
|
|
3441
|
+
"CWE-284"
|
|
3442
|
+
],
|
|
3443
|
+
"recommendation": "Add authentication middleware to the route",
|
|
3444
|
+
"status": "confirmed",
|
|
3445
|
+
"verification": {
|
|
3446
|
+
"verdict": "confirmed",
|
|
3447
|
+
"reason_code": "traced",
|
|
3448
|
+
"checked": [
|
|
3449
|
+
"php-laravel/routes/web.php",
|
|
3450
|
+
"php-laravel/app/Http/Kernel.php"
|
|
3451
|
+
],
|
|
3452
|
+
"control_found": null,
|
|
3453
|
+
"notes": "The route is defined without the 'auth' middleware and is not inside a group that applies authentication. No guard or middleware enforces that the user is logged in before the controller action is executed.",
|
|
3454
|
+
"suggested_subtype": null,
|
|
3455
|
+
"suggested_cwe": null
|
|
3456
|
+
},
|
|
3457
|
+
"consensus": {
|
|
3458
|
+
"score": 0.95,
|
|
3459
|
+
"agreement_level": "unanimous",
|
|
3460
|
+
"voted_models": [
|
|
3461
|
+
"gpt-oss:20b"
|
|
3462
|
+
],
|
|
3463
|
+
"rationale": "No authentication guard found; the endpoint is vulnerable to BOLA."
|
|
3464
|
+
},
|
|
3465
|
+
"fingerprint": "kfp1:a75c4d57b2a6479b2e4c90b8d6bd32aaabb8b13b152a911ea186a013c4b73c12",
|
|
3466
|
+
"baseline_state": "new"
|
|
3467
|
+
},
|
|
3468
|
+
{
|
|
3469
|
+
"finding_id": "BOLA-R-0053-1",
|
|
3470
|
+
"category": "bola",
|
|
3471
|
+
"subtype": "no_authentication",
|
|
3472
|
+
"title": "No authentication on POST /posts",
|
|
3473
|
+
"route_id": "R-0053",
|
|
3474
|
+
"endpoint": {
|
|
3475
|
+
"method": "POST",
|
|
3476
|
+
"path": "/posts"
|
|
3477
|
+
},
|
|
3478
|
+
"location": {
|
|
3479
|
+
"file": "php-laravel/routes/web.php",
|
|
3480
|
+
"start_line": 7
|
|
3481
|
+
},
|
|
3482
|
+
"evidence": [
|
|
3483
|
+
{
|
|
3484
|
+
"file": "php-laravel/routes/web.php",
|
|
3485
|
+
"start_line": 7,
|
|
3486
|
+
"code": "Route::post('/posts', [AccountController::class, 'post']);",
|
|
3487
|
+
"why": "Route is defined without auth middleware",
|
|
3488
|
+
"line": 7
|
|
3489
|
+
}
|
|
3490
|
+
],
|
|
3491
|
+
"severity": "high",
|
|
3492
|
+
"confidence": "high",
|
|
3493
|
+
"cwe": [
|
|
3494
|
+
"CWE-284"
|
|
3495
|
+
],
|
|
3496
|
+
"recommendation": "Add authentication middleware to the route",
|
|
3497
|
+
"status": "confirmed",
|
|
3498
|
+
"verification": {
|
|
3499
|
+
"verdict": "confirmed",
|
|
3500
|
+
"reason_code": "traced",
|
|
3501
|
+
"checked": [
|
|
3502
|
+
"php-laravel/routes/web.php",
|
|
3503
|
+
"php-laravel/app/Http/Kernel.php"
|
|
3504
|
+
],
|
|
3505
|
+
"control_found": null,
|
|
3506
|
+
"notes": "The POST /posts route is defined without any auth middleware and no global authentication middleware is registered in Kernel.php. No compensating control is present.",
|
|
3507
|
+
"suggested_subtype": null,
|
|
3508
|
+
"suggested_cwe": null
|
|
3509
|
+
},
|
|
3510
|
+
"consensus": {
|
|
3511
|
+
"score": 0.95,
|
|
3512
|
+
"agreement_level": "unanimous",
|
|
3513
|
+
"voted_models": [
|
|
3514
|
+
"gpt-oss:20b"
|
|
3515
|
+
],
|
|
3516
|
+
"rationale": "Both the route definition and the global middleware list were inspected and no authentication guard was found."
|
|
3517
|
+
},
|
|
3518
|
+
"fingerprint": "kfp1:e80d747a4f1fc8b6aa079fe8948e9bacaff6702c644b6d9c54f1bf6747461135",
|
|
3519
|
+
"baseline_state": "new"
|
|
3520
|
+
},
|
|
3521
|
+
{
|
|
3522
|
+
"finding_id": "BOLA-R-0054-1",
|
|
3523
|
+
"category": "bola",
|
|
3524
|
+
"subtype": "no_authentication",
|
|
3525
|
+
"title": "No authentication on POST /teams",
|
|
3526
|
+
"route_id": "R-0054",
|
|
3527
|
+
"endpoint": {
|
|
3528
|
+
"method": "POST",
|
|
3529
|
+
"path": "/teams"
|
|
3530
|
+
},
|
|
3531
|
+
"location": {
|
|
3532
|
+
"file": "php-laravel/routes/web.php",
|
|
3533
|
+
"start_line": 8
|
|
3534
|
+
},
|
|
3535
|
+
"evidence": [
|
|
3536
|
+
{
|
|
3537
|
+
"file": "php-laravel/routes/web.php",
|
|
3538
|
+
"start_line": 8,
|
|
3539
|
+
"code": "Route::post('/teams', [AccountController::class, 'team']);",
|
|
3540
|
+
"why": "Route is defined without auth middleware",
|
|
3541
|
+
"line": 8
|
|
3542
|
+
}
|
|
3543
|
+
],
|
|
3544
|
+
"severity": "high",
|
|
3545
|
+
"confidence": "high",
|
|
3546
|
+
"cwe": [
|
|
3547
|
+
"CWE-284"
|
|
3548
|
+
],
|
|
3549
|
+
"recommendation": "Add authentication middleware to the route",
|
|
3550
|
+
"status": "confirmed",
|
|
3551
|
+
"verification": {
|
|
3552
|
+
"verdict": "confirmed",
|
|
3553
|
+
"reason_code": "traced",
|
|
3554
|
+
"checked": [
|
|
3555
|
+
"php-laravel/routes/web.php",
|
|
3556
|
+
"php-laravel/app/Http/Kernel.php"
|
|
3557
|
+
],
|
|
3558
|
+
"control_found": null,
|
|
3559
|
+
"notes": "The POST /teams route is also defined without auth middleware and no global auth guard exists.",
|
|
3560
|
+
"suggested_subtype": null,
|
|
3561
|
+
"suggested_cwe": null
|
|
3562
|
+
},
|
|
3563
|
+
"consensus": {
|
|
3564
|
+
"score": 0.95,
|
|
3565
|
+
"agreement_level": "unanimous",
|
|
3566
|
+
"voted_models": [
|
|
3567
|
+
"gpt-oss:20b"
|
|
3568
|
+
],
|
|
3569
|
+
"rationale": "Same reasoning as for BOLA-R-0053-1."
|
|
3570
|
+
},
|
|
3571
|
+
"fingerprint": "kfp1:7807070b16945966eada9bf5f0be55e5fe6a154026cd3964497166e49879ae43",
|
|
3572
|
+
"baseline_state": "new"
|
|
3573
|
+
},
|
|
3574
|
+
{
|
|
3575
|
+
"finding_id": "BOLA-R-0055-1",
|
|
3576
|
+
"category": "bola",
|
|
3577
|
+
"subtype": "no_authentication",
|
|
3578
|
+
"title": "No authentication on POST /account/rename",
|
|
3579
|
+
"route_id": "R-0055",
|
|
3580
|
+
"endpoint": {
|
|
3581
|
+
"method": "POST",
|
|
3582
|
+
"path": "/account/rename"
|
|
3583
|
+
},
|
|
3584
|
+
"location": {
|
|
3585
|
+
"file": "php-laravel/routes/web.php",
|
|
3586
|
+
"start_line": 9
|
|
3587
|
+
},
|
|
3588
|
+
"evidence": [
|
|
3589
|
+
{
|
|
3590
|
+
"file": "php-laravel/routes/web.php",
|
|
3591
|
+
"start_line": 9,
|
|
3592
|
+
"code": "Route::post('/account/rename', [AccountController::class, 'rename']);",
|
|
3593
|
+
"why": "Route is defined without auth middleware",
|
|
3594
|
+
"line": 9
|
|
3595
|
+
}
|
|
3596
|
+
],
|
|
3597
|
+
"severity": "high",
|
|
3598
|
+
"confidence": "high",
|
|
3599
|
+
"cwe": [
|
|
3600
|
+
"CWE-284"
|
|
3601
|
+
],
|
|
3602
|
+
"recommendation": "Add authentication middleware to the route",
|
|
3603
|
+
"status": "confirmed",
|
|
3604
|
+
"verification": {
|
|
3605
|
+
"verdict": "confirmed",
|
|
3606
|
+
"reason_code": "traced",
|
|
3607
|
+
"checked": [
|
|
3608
|
+
"php-laravel/routes/web.php",
|
|
3609
|
+
"php-laravel/app/Http/Kernel.php"
|
|
3610
|
+
],
|
|
3611
|
+
"control_found": null,
|
|
3612
|
+
"notes": "The route is defined without the 'auth' middleware and is not inside a group that applies authentication. No guard or middleware enforces that the user is logged in before the controller action is executed.",
|
|
3613
|
+
"suggested_subtype": null,
|
|
3614
|
+
"suggested_cwe": null
|
|
3615
|
+
},
|
|
3616
|
+
"consensus": {
|
|
3617
|
+
"score": 0.95,
|
|
3618
|
+
"agreement_level": "unanimous",
|
|
3619
|
+
"voted_models": [
|
|
3620
|
+
"gpt-oss:20b"
|
|
3621
|
+
],
|
|
3622
|
+
"rationale": "No authentication guard found; the endpoint is vulnerable to BOLA."
|
|
3623
|
+
},
|
|
3624
|
+
"fingerprint": "kfp1:8470d18ce6052644276604e5fadb06c1d1c92c9ff637d1e3264b0063299fbdc4",
|
|
3625
|
+
"baseline_state": "new"
|
|
3626
|
+
},
|
|
3627
|
+
{
|
|
3628
|
+
"finding_id": "BOLA-R-0056-1",
|
|
3629
|
+
"category": "bola",
|
|
3630
|
+
"subtype": "no_authentication",
|
|
3631
|
+
"title": "No authentication on POST /account/profile",
|
|
3632
|
+
"route_id": "R-0056",
|
|
3633
|
+
"endpoint": {
|
|
3634
|
+
"method": "POST",
|
|
3635
|
+
"path": "/account/profile"
|
|
3636
|
+
},
|
|
3637
|
+
"location": {
|
|
3638
|
+
"file": "php-laravel/routes/web.php",
|
|
3639
|
+
"start_line": 10
|
|
3640
|
+
},
|
|
3641
|
+
"evidence": [
|
|
3642
|
+
{
|
|
3643
|
+
"file": "php-laravel/routes/web.php",
|
|
3644
|
+
"start_line": 10,
|
|
3645
|
+
"code": "Route::post('/account/profile', [AccountController::class, 'profile']);",
|
|
3646
|
+
"why": "Route is defined without auth middleware",
|
|
3647
|
+
"line": 10
|
|
3648
|
+
}
|
|
3649
|
+
],
|
|
3650
|
+
"severity": "high",
|
|
3651
|
+
"confidence": "high",
|
|
3652
|
+
"cwe": [
|
|
3653
|
+
"CWE-284"
|
|
3654
|
+
],
|
|
3655
|
+
"recommendation": "Add authentication middleware to the route",
|
|
3656
|
+
"status": "confirmed",
|
|
3657
|
+
"verification": {
|
|
3658
|
+
"verdict": "confirmed",
|
|
3659
|
+
"reason_code": "traced",
|
|
3660
|
+
"checked": [
|
|
3661
|
+
"php-laravel/routes/web.php",
|
|
3662
|
+
"php-laravel/app/Http/Kernel.php"
|
|
3663
|
+
],
|
|
3664
|
+
"control_found": null,
|
|
3665
|
+
"notes": "The route is defined without the 'auth' middleware and is not inside a group that applies authentication. No guard or middleware enforces that the user is logged in before the controller action is executed.",
|
|
3666
|
+
"suggested_subtype": null,
|
|
3667
|
+
"suggested_cwe": null
|
|
3668
|
+
},
|
|
3669
|
+
"consensus": {
|
|
3670
|
+
"score": 0.95,
|
|
3671
|
+
"agreement_level": "unanimous",
|
|
3672
|
+
"voted_models": [
|
|
3673
|
+
"gpt-oss:20b"
|
|
3674
|
+
],
|
|
3675
|
+
"rationale": "No authentication guard found; the endpoint is vulnerable to BOLA."
|
|
3676
|
+
},
|
|
3677
|
+
"fingerprint": "kfp1:aba8d638a3ae4a0069bfa4f418b000eccfe2124b3fc0963b6a126dc99618903d",
|
|
3678
|
+
"baseline_state": "new"
|
|
3679
|
+
},
|
|
3680
|
+
{
|
|
3681
|
+
"finding_id": "BOLA-R-0060-1",
|
|
3682
|
+
"category": "bola",
|
|
3683
|
+
"subtype": "missing_ownership_check",
|
|
3684
|
+
"title": "BOLA in GET /php-plain/mongo_find.php: missing owner verification",
|
|
3685
|
+
"route_id": "R-0060",
|
|
3686
|
+
"endpoint": {
|
|
3687
|
+
"method": "GET",
|
|
3688
|
+
"path": "/php-plain/mongo_find.php"
|
|
3689
|
+
},
|
|
3690
|
+
"location": {
|
|
3691
|
+
"file": "php-plain/mongo_find.php",
|
|
3692
|
+
"start_line": 5
|
|
3693
|
+
},
|
|
3694
|
+
"evidence": [
|
|
3695
|
+
{
|
|
3696
|
+
"file": "php-plain/mongo_find.php",
|
|
3697
|
+
"start_line": 5,
|
|
3698
|
+
"code": "$doc = $coll->findOne(array('login' => $_GET['login'], 'pass' => $_GET['pass']));",
|
|
3699
|
+
"why": "The query uses user-supplied credentials to retrieve any member document without restricting to the authenticated user.",
|
|
3700
|
+
"line": 5
|
|
3701
|
+
}
|
|
3702
|
+
],
|
|
3703
|
+
"severity": "high",
|
|
3704
|
+
"confidence": "high",
|
|
3705
|
+
"cwe": [
|
|
3706
|
+
"CWE-284",
|
|
3707
|
+
"CWE-639"
|
|
3708
|
+
],
|
|
3709
|
+
"recommendation": "Restrict the query to the current user's record or validate that the returned document belongs to the authenticated user.",
|
|
3710
|
+
"status": "confirmed",
|
|
3711
|
+
"verification": {
|
|
3712
|
+
"verdict": "confirmed",
|
|
3713
|
+
"reason_code": "traced",
|
|
3714
|
+
"checked": [
|
|
3715
|
+
"php-plain/mongo_find.php: lines 5-10",
|
|
3716
|
+
"php-plain/mongo_find.php: no session‑based filtering"
|
|
3717
|
+
],
|
|
3718
|
+
"control_found": null,
|
|
3719
|
+
"notes": "The query uses user‑supplied login and pass to retrieve any document. No check ties the result to the authenticated user.",
|
|
3720
|
+
"suggested_subtype": null,
|
|
3721
|
+
"suggested_cwe": null
|
|
3722
|
+
},
|
|
3723
|
+
"consensus": {
|
|
3724
|
+
"score": 0.95,
|
|
3725
|
+
"agreement_level": "unanimous",
|
|
3726
|
+
"voted_models": [
|
|
3727
|
+
"gpt-oss:20b"
|
|
3728
|
+
],
|
|
3729
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
3730
|
+
},
|
|
3731
|
+
"fingerprint": "kfp1:8089c7253436b57acdbe7c1394e8e4c58024655546f7d8d0ae2b107daae48ae3",
|
|
3732
|
+
"baseline_state": "new"
|
|
3733
|
+
},
|
|
3734
|
+
{
|
|
3735
|
+
"finding_id": "BOLA-R-0073-1",
|
|
3736
|
+
"category": "bola",
|
|
3737
|
+
"subtype": "missing_ownership_check",
|
|
3738
|
+
"title": "BOLA in POST /users/<int:uid>/name: missing owner verification",
|
|
3739
|
+
"route_id": "R-0073",
|
|
3740
|
+
"endpoint": {
|
|
3741
|
+
"method": "POST",
|
|
3742
|
+
"path": "/users/<int:uid>/name"
|
|
3743
|
+
},
|
|
3744
|
+
"location": {
|
|
3745
|
+
"file": "python-flask/profile.py",
|
|
3746
|
+
"start_line": 16
|
|
3747
|
+
},
|
|
3748
|
+
"evidence": [
|
|
3749
|
+
{
|
|
3750
|
+
"file": "python-flask/profile.py",
|
|
3751
|
+
"start_line": 16,
|
|
3752
|
+
"code": "user = User.query.get(uid)",
|
|
3753
|
+
"why": "The user ID comes from the request path and is used directly in a query without any check against the authenticated user.",
|
|
3754
|
+
"line": 16
|
|
3755
|
+
}
|
|
3756
|
+
],
|
|
3757
|
+
"severity": "high",
|
|
3758
|
+
"confidence": "high",
|
|
3759
|
+
"cwe": [
|
|
3760
|
+
"CWE-284",
|
|
3761
|
+
"CWE-639"
|
|
3762
|
+
],
|
|
3763
|
+
"recommendation": "Bind the query to the current session user or perform an explicit ownership check before updating.",
|
|
3764
|
+
"status": "confirmed",
|
|
3765
|
+
"verification": {
|
|
3766
|
+
"verdict": "confirmed",
|
|
3767
|
+
"reason_code": "traced",
|
|
3768
|
+
"checked": [
|
|
3769
|
+
"python-flask/profile.py: lines 16-20",
|
|
3770
|
+
"python-flask/__init__.py: global before_request (no uid check)"
|
|
3771
|
+
],
|
|
3772
|
+
"control_found": null,
|
|
3773
|
+
"notes": "The route is protected by @login_required but does not verify that the uid in the URL matches the authenticated user. No global filter or decorator enforces ownership, so the user can update any profile.",
|
|
3774
|
+
"suggested_subtype": null,
|
|
3775
|
+
"suggested_cwe": null
|
|
3776
|
+
},
|
|
3777
|
+
"consensus": {
|
|
3778
|
+
"score": 0.95,
|
|
3779
|
+
"agreement_level": "unanimous",
|
|
3780
|
+
"voted_models": [
|
|
3781
|
+
"gpt-oss:20b"
|
|
3782
|
+
],
|
|
3783
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
3784
|
+
},
|
|
3785
|
+
"fingerprint": "kfp1:bfc179df51db588076982a9b160cccb6aa5f7c21b296231a88eb6cbdfe2c3842",
|
|
3786
|
+
"baseline_state": "new"
|
|
3787
|
+
},
|
|
3788
|
+
{
|
|
3789
|
+
"finding_id": "BOLA-R-0074-1",
|
|
3790
|
+
"category": "bola",
|
|
3791
|
+
"subtype": "missing_ownership_check",
|
|
3792
|
+
"title": "BOLA in PATCH /users/<int:uid>: missing owner verification",
|
|
3793
|
+
"route_id": "R-0074",
|
|
3794
|
+
"endpoint": {
|
|
3795
|
+
"method": "PATCH",
|
|
3796
|
+
"path": "/users/<int:uid>"
|
|
3797
|
+
},
|
|
3798
|
+
"location": {
|
|
3799
|
+
"file": "python-flask/profile.py",
|
|
3800
|
+
"start_line": 24
|
|
3801
|
+
},
|
|
3802
|
+
"evidence": [
|
|
3803
|
+
{
|
|
3804
|
+
"file": "python-flask/profile.py",
|
|
3805
|
+
"start_line": 24,
|
|
3806
|
+
"code": "user = User.query.get(uid)",
|
|
3807
|
+
"why": "The user ID comes from the request path and is used directly to load a record without verifying ownership.",
|
|
3808
|
+
"line": 24
|
|
3809
|
+
}
|
|
3810
|
+
],
|
|
3811
|
+
"severity": "high",
|
|
3812
|
+
"confidence": "high",
|
|
3813
|
+
"cwe": [
|
|
3814
|
+
"CWE-284",
|
|
3815
|
+
"CWE-639"
|
|
3816
|
+
],
|
|
3817
|
+
"recommendation": "Ensure the record belongs to the authenticated user before applying changes.",
|
|
3818
|
+
"status": "confirmed",
|
|
3819
|
+
"verification": {
|
|
3820
|
+
"verdict": "confirmed",
|
|
3821
|
+
"reason_code": "traced",
|
|
3822
|
+
"checked": [
|
|
3823
|
+
"python-flask/profile.py: lines 24-28",
|
|
3824
|
+
"python-flask/__init__.py: global before_request (no uid check)"
|
|
3825
|
+
],
|
|
3826
|
+
"control_found": null,
|
|
3827
|
+
"notes": "Same ownership issue as R-0073: the uid is used directly to load a user record without verifying that it belongs to the current session user.",
|
|
3828
|
+
"suggested_subtype": null,
|
|
3829
|
+
"suggested_cwe": null
|
|
3830
|
+
},
|
|
3831
|
+
"consensus": {
|
|
3832
|
+
"score": 0.95,
|
|
3833
|
+
"agreement_level": "unanimous",
|
|
3834
|
+
"voted_models": [
|
|
3835
|
+
"gpt-oss:20b"
|
|
3836
|
+
],
|
|
3837
|
+
"rationale": "Independently audited and refuted false‑alarm candidates."
|
|
3838
|
+
},
|
|
3839
|
+
"fingerprint": "kfp1:53120a82329f249cb5d921adbb2d6965297c52b37440e5f47d8a2525bbe46fde",
|
|
3840
|
+
"baseline_state": "new"
|
|
3841
|
+
},
|
|
3842
|
+
{
|
|
3843
|
+
"finding_id": "BFLA-0023-1",
|
|
3844
|
+
"category": "bfla",
|
|
3845
|
+
"subtype": "missing_role_check",
|
|
3846
|
+
"title": "Spring MVC delete user action missing admin role check",
|
|
3847
|
+
"route_id": "R-0023",
|
|
3848
|
+
"endpoint": {
|
|
3849
|
+
"method": "POST",
|
|
3850
|
+
"path": "/admin/users/{id}/delete"
|
|
3851
|
+
},
|
|
3852
|
+
"location": {
|
|
3853
|
+
"file": "src/main/java/com/acme/web/AdminUserController.java",
|
|
3854
|
+
"start_line": 13,
|
|
3855
|
+
"end_line": 17
|
|
3856
|
+
},
|
|
3857
|
+
"evidence": [
|
|
3858
|
+
{
|
|
3859
|
+
"file": "src/main/java/com/acme/web/AdminUserController.java",
|
|
3860
|
+
"line": 13,
|
|
3861
|
+
"code": "@RequestMapping(value = \"/{id}/delete\", method = RequestMethod.POST)",
|
|
3862
|
+
"why": "no @PreAuthorize on method"
|
|
3863
|
+
}
|
|
3864
|
+
],
|
|
3865
|
+
"attack_scenario": "Any authenticated user can delete any user account.",
|
|
3866
|
+
"impact": "Privilege escalation to delete arbitrary user accounts.",
|
|
3867
|
+
"severity": "critical",
|
|
3868
|
+
"confidence": "high",
|
|
3869
|
+
"cwe": [
|
|
3870
|
+
"CWE-285"
|
|
3871
|
+
],
|
|
3872
|
+
"owasp": [
|
|
3873
|
+
"API5:2023"
|
|
3874
|
+
],
|
|
3875
|
+
"recommendation": "Add @PreAuthorize(\"hasRole('ADMIN')\") to the delete method.",
|
|
3876
|
+
"fix_example": "@PreAuthorize(\"hasRole('ADMIN')\") @RequestMapping(value = \"/{id}/delete\", method = RequestMethod.POST) public String delete(@PathVariable(\"id\") Long id) { … }",
|
|
3877
|
+
"status": "rejected",
|
|
3878
|
+
"verification": {
|
|
3879
|
+
"verdict": "rejected",
|
|
3880
|
+
"reason_code": "control_found",
|
|
3881
|
+
"checked": [
|
|
3882
|
+
"src/main/java/com/acme/web/AdminUserController.java lines 13-17",
|
|
3883
|
+
"src/main/java/com/acme/web/SecurityConfig.java for antMatchers",
|
|
3884
|
+
"src/main/java/com/acme/web/SecurityConfig.java for hasRole('ADMIN')"
|
|
3885
|
+
],
|
|
3886
|
+
"control_found": {
|
|
3887
|
+
"file": "src/main/java/com/acme/web/SecurityConfig.java",
|
|
3888
|
+
"line": 42,
|
|
3889
|
+
"code": "antMatchers(\"/admin/**\").hasRole(\"ADMIN\")"
|
|
3890
|
+
},
|
|
3891
|
+
"notes": "Spring Security configuration restricts all `/admin/**` URLs to users with the `ADMIN` role via `antMatchers(\"/admin/**\").hasRole(\"ADMIN\")`. The DeleteUser action is therefore protected by this global rule, mitigating the reported BFLA.",
|
|
3892
|
+
"suggested_subtype": null,
|
|
3893
|
+
"suggested_cwe": null
|
|
3894
|
+
},
|
|
3895
|
+
"consensus": {
|
|
3896
|
+
"score": 0.95,
|
|
3897
|
+
"agreement_level": "unanimous",
|
|
3898
|
+
"voted_models": [
|
|
3899
|
+
"gpt-oss:20b"
|
|
3900
|
+
],
|
|
3901
|
+
"rationale": "Global security rule provides the missing role check."
|
|
3902
|
+
},
|
|
3903
|
+
"fingerprint": "kfp1:059194dfb818722c86a3d5a30d55a760cd2cf30a78794018b70813afdf76967a"
|
|
3904
|
+
},
|
|
3905
|
+
{
|
|
3906
|
+
"finding_id": "INJ-I-0006-1",
|
|
3907
|
+
"category": "sql_injection",
|
|
3908
|
+
"subtype": "string_concatenation",
|
|
3909
|
+
"title": "SQL Injection in coldfusion/app/OrderService.cfc:5",
|
|
3910
|
+
"candidate_id": "I-0006",
|
|
3911
|
+
"location": {
|
|
3912
|
+
"file": "coldfusion/app/OrderService.cfc",
|
|
3913
|
+
"start_line": 5
|
|
3914
|
+
},
|
|
3915
|
+
"evidence": [
|
|
3916
|
+
{
|
|
3917
|
+
"file": "coldfusion/app/OrderService.cfc",
|
|
3918
|
+
"start_line": 5,
|
|
3919
|
+
"code": "SELECT * FROM orders WHERE id = #arguments.orderId#",
|
|
3920
|
+
"why": "Unsanitized input concatenated into query",
|
|
3921
|
+
"line": 5
|
|
3922
|
+
}
|
|
3923
|
+
],
|
|
3924
|
+
"severity": "critical",
|
|
3925
|
+
"confidence": "high",
|
|
3926
|
+
"cwe": [
|
|
3927
|
+
"CWE-89"
|
|
3928
|
+
],
|
|
3929
|
+
"recommendation": "Use cfqueryparam to bind parameters",
|
|
3930
|
+
"status": "rejected",
|
|
3931
|
+
"verification": {
|
|
3932
|
+
"verdict": "rejected",
|
|
3933
|
+
"reason_code": "control_found",
|
|
3934
|
+
"checked": [
|
|
3935
|
+
"coldfusion/app/OrderService.cfc line 3",
|
|
3936
|
+
"argument definition for orderId",
|
|
3937
|
+
"ColdFusion type enforcement"
|
|
3938
|
+
],
|
|
3939
|
+
"control_found": {
|
|
3940
|
+
"file": "coldfusion/app/OrderService.cfc",
|
|
3941
|
+
"line": 3,
|
|
3942
|
+
"code": "<cfargument name=\"orderId\" type=\"numeric\" required=\"true\" />"
|
|
3943
|
+
},
|
|
3944
|
+
"notes": "The `orderId` argument is declared with type `numeric`. ColdFusion automatically casts the value to a number before interpolation, preventing SQL injection. Therefore the concatenation is safe.",
|
|
3945
|
+
"suggested_subtype": null,
|
|
3946
|
+
"suggested_cwe": null
|
|
3947
|
+
},
|
|
3948
|
+
"consensus": {
|
|
3949
|
+
"score": 0.95,
|
|
3950
|
+
"agreement_level": "unanimous",
|
|
3951
|
+
"voted_models": [
|
|
3952
|
+
"gpt-oss:20b"
|
|
3953
|
+
],
|
|
3954
|
+
"rationale": "ColdFusion numeric type enforcement mitigates injection."
|
|
3955
|
+
},
|
|
3956
|
+
"fingerprint": "kfp1:b6824160d6d38156b7a16dc1526dda6be874be4401ad843a3c185f94446dcf5e"
|
|
3957
|
+
},
|
|
3958
|
+
{
|
|
3959
|
+
"finding_id": "BOLA-R-0057-1",
|
|
3960
|
+
"category": "bola",
|
|
3961
|
+
"subtype": "missing_ownership_check",
|
|
3962
|
+
"title": "BOLA in GET /php-plain/admin.php?action=view: missing owner verification",
|
|
3963
|
+
"route_id": "R-0057",
|
|
3964
|
+
"endpoint": {
|
|
3965
|
+
"method": "GET",
|
|
3966
|
+
"path": "/php-plain/admin.php?action=view"
|
|
3967
|
+
},
|
|
3968
|
+
"location": {
|
|
3969
|
+
"file": "php-plain/admin.php",
|
|
3970
|
+
"start_line": 6
|
|
3971
|
+
},
|
|
3972
|
+
"evidence": [
|
|
3973
|
+
{
|
|
3974
|
+
"file": "php-plain/admin.php",
|
|
3975
|
+
"start_line": 6,
|
|
3976
|
+
"code": "$uid = (int)$_GET['uid'];",
|
|
3977
|
+
"why": "Identifier comes from request",
|
|
3978
|
+
"line": 6
|
|
3979
|
+
},
|
|
3980
|
+
{
|
|
3981
|
+
"file": "php-plain/admin.php",
|
|
3982
|
+
"start_line": 7,
|
|
3983
|
+
"code": "$r = mysql_query(\"SELECT * FROM users WHERE id = \" . $uid);",
|
|
3984
|
+
"why": "Query does not bind to current user; any user can view any record",
|
|
3985
|
+
"line": 7
|
|
3986
|
+
}
|
|
3987
|
+
],
|
|
3988
|
+
"severity": "high",
|
|
3989
|
+
"confidence": "high",
|
|
3990
|
+
"cwe": [
|
|
3991
|
+
"CWE-639",
|
|
3992
|
+
"CWE-284"
|
|
3993
|
+
],
|
|
3994
|
+
"recommendation": "Bind query to current session user",
|
|
3995
|
+
"status": "rejected",
|
|
3996
|
+
"verification": {
|
|
3997
|
+
"verdict": "rejected",
|
|
3998
|
+
"reason_code": "control_found",
|
|
3999
|
+
"checked": [
|
|
4000
|
+
"php-plain/admin.php"
|
|
4001
|
+
],
|
|
4002
|
+
"control_found": {
|
|
4003
|
+
"file": "php-plain/admin.php",
|
|
4004
|
+
"line": 2,
|
|
4005
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4006
|
+
},
|
|
4007
|
+
"notes": "The file begins with a session check that redirects unauthenticated users to the login page, providing a compensating authentication control.",
|
|
4008
|
+
"suggested_subtype": null,
|
|
4009
|
+
"suggested_cwe": null
|
|
4010
|
+
},
|
|
4011
|
+
"consensus": {
|
|
4012
|
+
"score": 0.95,
|
|
4013
|
+
"agreement_level": "unanimous",
|
|
4014
|
+
"voted_models": [
|
|
4015
|
+
"gpt-oss:20b"
|
|
4016
|
+
],
|
|
4017
|
+
"rationale": "The session guard is explicitly present and executed before any database access."
|
|
4018
|
+
},
|
|
4019
|
+
"fingerprint": "kfp1:3691f0046489f4fb4004e0df5818c6f613138b47d5675c7dc926255b0a186096"
|
|
4020
|
+
},
|
|
4021
|
+
{
|
|
4022
|
+
"finding_id": "BOLA-R-0057-2",
|
|
4023
|
+
"category": "bola",
|
|
4024
|
+
"subtype": "missing_ownership_check",
|
|
4025
|
+
"title": "BOLA in POST /php-plain/admin.php?action=delete: missing owner verification",
|
|
4026
|
+
"route_id": "R-0057",
|
|
4027
|
+
"endpoint": {
|
|
4028
|
+
"method": "POST",
|
|
4029
|
+
"path": "/php-plain/admin.php?action=delete"
|
|
4030
|
+
},
|
|
4031
|
+
"location": {
|
|
4032
|
+
"file": "php-plain/admin.php",
|
|
4033
|
+
"start_line": 10
|
|
4034
|
+
},
|
|
4035
|
+
"evidence": [
|
|
4036
|
+
{
|
|
4037
|
+
"file": "php-plain/admin.php",
|
|
4038
|
+
"start_line": 10,
|
|
4039
|
+
"code": "$id = $_POST['id'];",
|
|
4040
|
+
"why": "Identifier comes from request",
|
|
4041
|
+
"line": 10
|
|
4042
|
+
},
|
|
4043
|
+
{
|
|
4044
|
+
"file": "php-plain/admin.php",
|
|
4045
|
+
"start_line": 11,
|
|
4046
|
+
"code": "mysql_query(\"DELETE FROM users WHERE id = $id\");",
|
|
4047
|
+
"why": "Delete query does not bind to current user; any user can delete any record",
|
|
4048
|
+
"line": 11
|
|
4049
|
+
}
|
|
4050
|
+
],
|
|
4051
|
+
"severity": "high",
|
|
4052
|
+
"confidence": "high",
|
|
4053
|
+
"cwe": [
|
|
4054
|
+
"CWE-639",
|
|
4055
|
+
"CWE-284"
|
|
4056
|
+
],
|
|
4057
|
+
"recommendation": "Bind delete to current session user",
|
|
4058
|
+
"status": "rejected",
|
|
4059
|
+
"verification": {
|
|
4060
|
+
"verdict": "rejected",
|
|
4061
|
+
"reason_code": "control_found",
|
|
4062
|
+
"checked": [
|
|
4063
|
+
"php-plain/admin.php"
|
|
4064
|
+
],
|
|
4065
|
+
"control_found": {
|
|
4066
|
+
"file": "php-plain/admin.php",
|
|
4067
|
+
"line": 2,
|
|
4068
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4069
|
+
},
|
|
4070
|
+
"notes": "Same session authentication guard as in BOLA-R-0057-1 protects the delete action.",
|
|
4071
|
+
"suggested_subtype": null,
|
|
4072
|
+
"suggested_cwe": null
|
|
4073
|
+
},
|
|
4074
|
+
"consensus": {
|
|
4075
|
+
"score": 0.95,
|
|
4076
|
+
"agreement_level": "unanimous",
|
|
4077
|
+
"voted_models": [
|
|
4078
|
+
"gpt-oss:20b"
|
|
4079
|
+
],
|
|
4080
|
+
"rationale": "The guard is applied before any POST handling."
|
|
4081
|
+
},
|
|
4082
|
+
"fingerprint": "kfp1:00dddf4b55c567cdf4432ca9180652ef14141f6a7209f530d04973f172a383e8"
|
|
4083
|
+
},
|
|
4084
|
+
{
|
|
4085
|
+
"finding_id": "BOLA-R-0058-1",
|
|
4086
|
+
"category": "bola",
|
|
4087
|
+
"subtype": "missing_ownership_check",
|
|
4088
|
+
"title": "BOLA in POST /php-plain/admin.php?action=delete: missing owner verification",
|
|
4089
|
+
"route_id": "R-0058",
|
|
4090
|
+
"endpoint": {
|
|
4091
|
+
"method": "POST",
|
|
4092
|
+
"path": "/php-plain/admin.php?action=delete"
|
|
4093
|
+
},
|
|
4094
|
+
"location": {
|
|
4095
|
+
"file": "php-plain/admin.php",
|
|
4096
|
+
"start_line": 10
|
|
4097
|
+
},
|
|
4098
|
+
"evidence": [
|
|
4099
|
+
{
|
|
4100
|
+
"file": "php-plain/admin.php",
|
|
4101
|
+
"start_line": 10,
|
|
4102
|
+
"code": "$id = $_POST['id'];",
|
|
4103
|
+
"why": "Identifier comes from request",
|
|
4104
|
+
"line": 10
|
|
4105
|
+
},
|
|
4106
|
+
{
|
|
4107
|
+
"file": "php-plain/admin.php",
|
|
4108
|
+
"start_line": 11,
|
|
4109
|
+
"code": "mysql_query(\"DELETE FROM users WHERE id = $id\");",
|
|
4110
|
+
"why": "Delete query does not bind to current user; any user can delete any record",
|
|
4111
|
+
"line": 11
|
|
4112
|
+
}
|
|
4113
|
+
],
|
|
4114
|
+
"severity": "high",
|
|
4115
|
+
"confidence": "high",
|
|
4116
|
+
"cwe": [
|
|
4117
|
+
"CWE-639",
|
|
4118
|
+
"CWE-284"
|
|
4119
|
+
],
|
|
4120
|
+
"recommendation": "Bind delete to current session user",
|
|
4121
|
+
"status": "rejected",
|
|
4122
|
+
"verification": {
|
|
4123
|
+
"verdict": "rejected",
|
|
4124
|
+
"reason_code": "control_found",
|
|
4125
|
+
"checked": [
|
|
4126
|
+
"php-plain/admin.php"
|
|
4127
|
+
],
|
|
4128
|
+
"control_found": {
|
|
4129
|
+
"file": "php-plain/admin.php",
|
|
4130
|
+
"line": 2,
|
|
4131
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4132
|
+
},
|
|
4133
|
+
"notes": "Identical to BOLA-R-0057-2; the session guard protects the delete action.",
|
|
4134
|
+
"suggested_subtype": null,
|
|
4135
|
+
"suggested_cwe": null
|
|
4136
|
+
},
|
|
4137
|
+
"consensus": {
|
|
4138
|
+
"score": 0.95,
|
|
4139
|
+
"agreement_level": "unanimous",
|
|
4140
|
+
"voted_models": [
|
|
4141
|
+
"gpt-oss:20b"
|
|
4142
|
+
],
|
|
4143
|
+
"rationale": "Same as above."
|
|
4144
|
+
},
|
|
4145
|
+
"fingerprint": "kfp1:f5b461e1aa247ef88e1a2336d2c47dfe19a93f9c82e8111cfd4c41468f95ce7c"
|
|
4146
|
+
},
|
|
4147
|
+
{
|
|
4148
|
+
"finding_id": "BOLA-R-0059-1",
|
|
4149
|
+
"category": "bola",
|
|
4150
|
+
"subtype": "missing_ownership_check",
|
|
4151
|
+
"title": "BOLA in POST /php-plain/manage_users.php: missing owner verification",
|
|
4152
|
+
"route_id": "R-0059",
|
|
4153
|
+
"endpoint": {
|
|
4154
|
+
"method": "POST",
|
|
4155
|
+
"path": "/php-plain/manage_users.php"
|
|
4156
|
+
},
|
|
4157
|
+
"location": {
|
|
4158
|
+
"file": "php-plain/manage_users.php",
|
|
4159
|
+
"start_line": 5
|
|
4160
|
+
},
|
|
4161
|
+
"evidence": [
|
|
4162
|
+
{
|
|
4163
|
+
"file": "php-plain/manage_users.php",
|
|
4164
|
+
"start_line": 5,
|
|
4165
|
+
"code": "if ($_SERVER['REQUEST_METHOD'] === 'POST') {",
|
|
4166
|
+
"why": "Identifier comes from request",
|
|
4167
|
+
"line": 5
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
"file": "php-plain/manage_users.php",
|
|
4171
|
+
"start_line": 6,
|
|
4172
|
+
"code": "$stmt = $db->prepare(\"DELETE FROM users WHERE id = ?\");",
|
|
4173
|
+
"why": "Delete query does not bind to current user",
|
|
4174
|
+
"line": 6
|
|
4175
|
+
},
|
|
4176
|
+
{
|
|
4177
|
+
"file": "php-plain/manage_users.php",
|
|
4178
|
+
"start_line": 7,
|
|
4179
|
+
"code": "$stmt->execute(array((int)$_POST['id']));",
|
|
4180
|
+
"why": "Execution uses unbound identifier",
|
|
4181
|
+
"line": 7
|
|
4182
|
+
}
|
|
4183
|
+
],
|
|
4184
|
+
"severity": "high",
|
|
4185
|
+
"confidence": "high",
|
|
4186
|
+
"cwe": [
|
|
4187
|
+
"CWE-639",
|
|
4188
|
+
"CWE-284"
|
|
4189
|
+
],
|
|
4190
|
+
"recommendation": "Bind delete to current session user",
|
|
4191
|
+
"status": "rejected",
|
|
4192
|
+
"verification": {
|
|
4193
|
+
"verdict": "rejected",
|
|
4194
|
+
"reason_code": "control_found",
|
|
4195
|
+
"checked": [
|
|
4196
|
+
"php-plain/manage_users.php"
|
|
4197
|
+
],
|
|
4198
|
+
"control_found": {
|
|
4199
|
+
"file": "php-plain/manage_users.php",
|
|
4200
|
+
"line": 2,
|
|
4201
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4202
|
+
},
|
|
4203
|
+
"notes": "The script begins with a session authentication guard that blocks unauthenticated access.",
|
|
4204
|
+
"suggested_subtype": null,
|
|
4205
|
+
"suggested_cwe": null
|
|
4206
|
+
},
|
|
4207
|
+
"consensus": {
|
|
4208
|
+
"score": 0.95,
|
|
4209
|
+
"agreement_level": "unanimous",
|
|
4210
|
+
"voted_models": [
|
|
4211
|
+
"gpt-oss:20b"
|
|
4212
|
+
],
|
|
4213
|
+
"rationale": "Guard executed before any delete operation."
|
|
4214
|
+
},
|
|
4215
|
+
"fingerprint": "kfp1:08e7e40b1898b061e12415cfde4fc28a2ba729418d4c548f7e22b68a4056e0cc"
|
|
4216
|
+
},
|
|
4217
|
+
{
|
|
4218
|
+
"finding_id": "BOLA-R-0061-1",
|
|
4219
|
+
"category": "bola",
|
|
4220
|
+
"subtype": "missing_ownership_check",
|
|
4221
|
+
"title": "BOLA in GET /php-plain/order_detail.php: missing owner verification",
|
|
4222
|
+
"route_id": "R-0061",
|
|
4223
|
+
"endpoint": {
|
|
4224
|
+
"method": "GET",
|
|
4225
|
+
"path": "/php-plain/order_detail.php"
|
|
4226
|
+
},
|
|
4227
|
+
"location": {
|
|
4228
|
+
"file": "php-plain/order_detail.php",
|
|
4229
|
+
"start_line": 5
|
|
4230
|
+
},
|
|
4231
|
+
"evidence": [
|
|
4232
|
+
{
|
|
4233
|
+
"file": "php-plain/order_detail.php",
|
|
4234
|
+
"start_line": 5,
|
|
4235
|
+
"code": "$id = $_GET['id'];",
|
|
4236
|
+
"why": "Identifier comes from request",
|
|
4237
|
+
"line": 5
|
|
4238
|
+
},
|
|
4239
|
+
{
|
|
4240
|
+
"file": "php-plain/order_detail.php",
|
|
4241
|
+
"start_line": 6,
|
|
4242
|
+
"code": "$res = mysql_query(\"SELECT * FROM orders WHERE id = \" . $id);",
|
|
4243
|
+
"why": "Query does not bind to current user; any user can read any order",
|
|
4244
|
+
"line": 6
|
|
4245
|
+
}
|
|
4246
|
+
],
|
|
4247
|
+
"severity": "high",
|
|
4248
|
+
"confidence": "high",
|
|
4249
|
+
"cwe": [
|
|
4250
|
+
"CWE-639",
|
|
4251
|
+
"CWE-284"
|
|
4252
|
+
],
|
|
4253
|
+
"recommendation": "Bind query to current session user",
|
|
4254
|
+
"status": "rejected",
|
|
4255
|
+
"verification": {
|
|
4256
|
+
"verdict": "rejected",
|
|
4257
|
+
"reason_code": "control_found",
|
|
4258
|
+
"checked": [
|
|
4259
|
+
"php-plain/order_detail.php"
|
|
4260
|
+
],
|
|
4261
|
+
"control_found": {
|
|
4262
|
+
"file": "php-plain/order_detail.php",
|
|
4263
|
+
"line": 2,
|
|
4264
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4265
|
+
},
|
|
4266
|
+
"notes": "The file contains a session authentication check that prevents unauthenticated users from accessing order details.",
|
|
4267
|
+
"suggested_subtype": null,
|
|
4268
|
+
"suggested_cwe": null
|
|
4269
|
+
},
|
|
4270
|
+
"consensus": {
|
|
4271
|
+
"score": 0.95,
|
|
4272
|
+
"agreement_level": "unanimous",
|
|
4273
|
+
"voted_models": [
|
|
4274
|
+
"gpt-oss:20b"
|
|
4275
|
+
],
|
|
4276
|
+
"rationale": "Guard present before any database query."
|
|
4277
|
+
},
|
|
4278
|
+
"fingerprint": "kfp1:140efc93b08c193dd167db59f6a495c4cda56c967489cf32705bb8e227ed82e4"
|
|
4279
|
+
},
|
|
4280
|
+
{
|
|
4281
|
+
"finding_id": "BOLA-R-0066-1",
|
|
4282
|
+
"category": "bola",
|
|
4283
|
+
"subtype": "missing_ownership_check",
|
|
4284
|
+
"title": "BOLA in POST /php-plain/admin/delete_user.php: missing owner verification",
|
|
4285
|
+
"route_id": "R-0066",
|
|
4286
|
+
"endpoint": {
|
|
4287
|
+
"method": "POST",
|
|
4288
|
+
"path": "/php-plain/admin/delete_user.php"
|
|
4289
|
+
},
|
|
4290
|
+
"location": {
|
|
4291
|
+
"file": "php-plain/admin/delete_user.php",
|
|
4292
|
+
"start_line": 5
|
|
4293
|
+
},
|
|
4294
|
+
"evidence": [
|
|
4295
|
+
{
|
|
4296
|
+
"file": "php-plain/admin/delete_user.php",
|
|
4297
|
+
"start_line": 5,
|
|
4298
|
+
"code": "$id = (int)$_POST['id'];",
|
|
4299
|
+
"why": "Identifier comes from request",
|
|
4300
|
+
"line": 5
|
|
4301
|
+
},
|
|
4302
|
+
{
|
|
4303
|
+
"file": "php-plain/admin/delete_user.php",
|
|
4304
|
+
"start_line": 6,
|
|
4305
|
+
"code": "$stmt = $db->prepare(\"DELETE FROM users WHERE id = ?\");",
|
|
4306
|
+
"why": "Delete query does not bind to current user",
|
|
4307
|
+
"line": 6
|
|
4308
|
+
},
|
|
4309
|
+
{
|
|
4310
|
+
"file": "php-plain/admin/delete_user.php",
|
|
4311
|
+
"start_line": 7,
|
|
4312
|
+
"code": "$stmt->execute(array($id));",
|
|
4313
|
+
"why": "Execution uses unbound identifier",
|
|
4314
|
+
"line": 7
|
|
4315
|
+
}
|
|
4316
|
+
],
|
|
4317
|
+
"severity": "high",
|
|
4318
|
+
"confidence": "high",
|
|
4319
|
+
"cwe": [
|
|
4320
|
+
"CWE-639",
|
|
4321
|
+
"CWE-284"
|
|
4322
|
+
],
|
|
4323
|
+
"recommendation": "Bind delete to current session user",
|
|
4324
|
+
"status": "rejected",
|
|
4325
|
+
"verification": {
|
|
4326
|
+
"verdict": "rejected",
|
|
4327
|
+
"reason_code": "control_found",
|
|
4328
|
+
"checked": [
|
|
4329
|
+
"php-plain/admin/delete_user.php"
|
|
4330
|
+
],
|
|
4331
|
+
"control_found": {
|
|
4332
|
+
"file": "php-plain/admin/delete_user.php",
|
|
4333
|
+
"line": 2,
|
|
4334
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4335
|
+
},
|
|
4336
|
+
"notes": "The file starts with a session authentication check that ensures only logged‑in users can reach the delete logic.",
|
|
4337
|
+
"suggested_subtype": null,
|
|
4338
|
+
"suggested_cwe": null
|
|
4339
|
+
},
|
|
4340
|
+
"consensus": {
|
|
4341
|
+
"score": 0.95,
|
|
4342
|
+
"agreement_level": "unanimous",
|
|
4343
|
+
"voted_models": [
|
|
4344
|
+
"gpt-oss:20b"
|
|
4345
|
+
],
|
|
4346
|
+
"rationale": "Guard executed before any database operation."
|
|
4347
|
+
},
|
|
4348
|
+
"fingerprint": "kfp1:0da2f858297a3d4ebd54b94ece77760ab41c73280cf69a320b15b12ae8d88b7f"
|
|
4349
|
+
},
|
|
4350
|
+
{
|
|
4351
|
+
"finding_id": "BOLA-R-0067-1",
|
|
4352
|
+
"category": "bola",
|
|
4353
|
+
"subtype": "missing_ownership_check",
|
|
4354
|
+
"title": "BOLA in POST /php-plain/admin/promote.php: missing owner verification",
|
|
4355
|
+
"route_id": "R-0067",
|
|
4356
|
+
"endpoint": {
|
|
4357
|
+
"method": "POST",
|
|
4358
|
+
"path": "/php-plain/admin/promote.php"
|
|
4359
|
+
},
|
|
4360
|
+
"location": {
|
|
4361
|
+
"file": "php-plain/admin/promote.php",
|
|
4362
|
+
"start_line": 5
|
|
4363
|
+
},
|
|
4364
|
+
"evidence": [
|
|
4365
|
+
{
|
|
4366
|
+
"file": "php-plain/admin/promote.php",
|
|
4367
|
+
"start_line": 5,
|
|
4368
|
+
"code": "if ($_POST['is_admin'] == '1') {",
|
|
4369
|
+
"why": "Identifier comes from request",
|
|
4370
|
+
"line": 5
|
|
4371
|
+
},
|
|
4372
|
+
{
|
|
4373
|
+
"file": "php-plain/admin/promote.php",
|
|
4374
|
+
"start_line": 6,
|
|
4375
|
+
"code": "$stmt = $db->prepare(\"UPDATE users SET role = 'admin' WHERE id = ?\");",
|
|
4376
|
+
"why": "Update query does not bind to current user",
|
|
4377
|
+
"line": 6
|
|
4378
|
+
},
|
|
4379
|
+
{
|
|
4380
|
+
"file": "php-plain/admin/promote.php",
|
|
4381
|
+
"start_line": 7,
|
|
4382
|
+
"code": "$stmt->execute(array((int)$_POST['uid']));",
|
|
4383
|
+
"why": "Execution uses unbound identifier",
|
|
4384
|
+
"line": 7
|
|
4385
|
+
}
|
|
4386
|
+
],
|
|
4387
|
+
"severity": "high",
|
|
4388
|
+
"confidence": "high",
|
|
4389
|
+
"cwe": [
|
|
4390
|
+
"CWE-639",
|
|
4391
|
+
"CWE-284"
|
|
4392
|
+
],
|
|
4393
|
+
"recommendation": "Bind update to current session user",
|
|
4394
|
+
"status": "rejected",
|
|
4395
|
+
"verification": {
|
|
4396
|
+
"verdict": "rejected",
|
|
4397
|
+
"reason_code": "control_found",
|
|
4398
|
+
"checked": [
|
|
4399
|
+
"php-plain/admin/promote.php"
|
|
4400
|
+
],
|
|
4401
|
+
"control_found": {
|
|
4402
|
+
"file": "php-plain/admin/promote.php",
|
|
4403
|
+
"line": 2,
|
|
4404
|
+
"code": "if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; }"
|
|
4405
|
+
},
|
|
4406
|
+
"notes": "A session check at the top of the file protects the promotion endpoint.",
|
|
4407
|
+
"suggested_subtype": null,
|
|
4408
|
+
"suggested_cwe": null
|
|
4409
|
+
},
|
|
4410
|
+
"consensus": {
|
|
4411
|
+
"score": 0.95,
|
|
4412
|
+
"agreement_level": "unanimous",
|
|
4413
|
+
"voted_models": [
|
|
4414
|
+
"gpt-oss:20b"
|
|
4415
|
+
],
|
|
4416
|
+
"rationale": "Guard present before any role change logic."
|
|
4417
|
+
},
|
|
4418
|
+
"fingerprint": "kfp1:c015ee6be1af117790c78fc9a97374926e8a34a36361142150be94c318ff9dd8"
|
|
4419
|
+
},
|
|
4420
|
+
{
|
|
4421
|
+
"finding_id": "BOLA-R-0069-1",
|
|
4422
|
+
"category": "bola",
|
|
4423
|
+
"subtype": "missing_ownership_check",
|
|
4424
|
+
"title": "BOLA in GET /orders/<int:order_id>: missing owner verification",
|
|
4425
|
+
"route_id": "R-0069",
|
|
4426
|
+
"endpoint": {
|
|
4427
|
+
"method": "GET",
|
|
4428
|
+
"path": "/orders/<int:order_id>"
|
|
4429
|
+
},
|
|
4430
|
+
"location": {
|
|
4431
|
+
"file": "python-flask/app.py",
|
|
4432
|
+
"start_line": 12
|
|
4433
|
+
},
|
|
4434
|
+
"evidence": [
|
|
4435
|
+
{
|
|
4436
|
+
"file": "python-flask/app.py",
|
|
4437
|
+
"start_line": 12,
|
|
4438
|
+
"code": "cur.execute(\"SELECT * FROM orders WHERE id = %s\" % order_id)",
|
|
4439
|
+
"why": "Query does not bind to current user; any authenticated user can read any order",
|
|
4440
|
+
"line": 12
|
|
4441
|
+
}
|
|
4442
|
+
],
|
|
4443
|
+
"severity": "high",
|
|
4444
|
+
"confidence": "high",
|
|
4445
|
+
"cwe": [
|
|
4446
|
+
"CWE-639",
|
|
4447
|
+
"CWE-284"
|
|
4448
|
+
],
|
|
4449
|
+
"recommendation": "Bind query to current user",
|
|
4450
|
+
"status": "rejected",
|
|
4451
|
+
"verification": {
|
|
4452
|
+
"verdict": "rejected",
|
|
4453
|
+
"reason_code": "control_found",
|
|
4454
|
+
"checked": [
|
|
4455
|
+
"python-flask/app.py"
|
|
4456
|
+
],
|
|
4457
|
+
"control_found": {
|
|
4458
|
+
"file": "python-flask/app.py",
|
|
4459
|
+
"line": 10,
|
|
4460
|
+
"code": "@login_required"
|
|
4461
|
+
},
|
|
4462
|
+
"notes": "The route is decorated with @login_required, ensuring only authenticated users can reach the handler.",
|
|
4463
|
+
"suggested_subtype": null,
|
|
4464
|
+
"suggested_cwe": null
|
|
4465
|
+
},
|
|
4466
|
+
"consensus": {
|
|
4467
|
+
"score": 0.95,
|
|
4468
|
+
"agreement_level": "unanimous",
|
|
4469
|
+
"voted_models": [
|
|
4470
|
+
"gpt-oss:20b"
|
|
4471
|
+
],
|
|
4472
|
+
"rationale": "Authentication guard is applied directly to the route."
|
|
4473
|
+
},
|
|
4474
|
+
"fingerprint": "kfp1:8cba56db899ed30b4e1dab440f085921515c324f8d9fff2d15ee38eb60afa641"
|
|
4475
|
+
}
|
|
4476
|
+
]
|
|
4477
|
+
}
|