Code review at HEAD 7286d320. Security-001 (High): guard returnUrl with a local-URL
check before redirect (open-redirect/phishing vector) + regression test. Security-002:
update stale LdapOptions dev-LDAP doc reference.
3.9 KiB
Code Review — Security
| Field | Value |
|---|---|
| Module | src/Server/ZB.MOM.WW.OtOpcUa.Security |
| Reviewer | Claude Code |
| Review date | 2026-06-19 |
| Commit reviewed | 7286d320 |
| Status | Reviewed |
| Open findings | 0 |
Checklist coverage
A comprehensive review completes every category, recording "No issues found" where a category produced nothing rather than leaving it blank.
| # | Category | Result |
|---|---|---|
| 1 | Correctness & logic bugs | Security-001 (open redirect on successful login) |
| 2 | OtOpcUa conventions | No issues found |
| 3 | Concurrency & thread safety | No issues found |
| 4 | Error handling & resilience | No issues found |
| 5 | Security | Security-001 (open redirect), Security-002 (stale doc) |
| 6 | Performance & resource management | No issues found |
| 7 | Design-document adherence | No issues found |
| 8 | Code organization & conventions | No issues found |
| 9 | Testing coverage | Security-001 was untested (regression test added) |
| 10 | Documentation & comments | Security-002 (stale LdapOptions comment) |
Findings
Security-001
| Field | Value |
|---|---|
| Severity | High |
| Category | Security |
| Location | src/Server/ZB.MOM.WW.OtOpcUa.Security/Endpoints/AuthEndpoints.cs:135 |
| Status | Resolved |
Description: The /auth/login endpoint (form POST path) redirects to returnUrl on successful authentication without validating that the URL is local to the application. An attacker can craft a phishing link such as https://admin.example.com/login?returnUrl=https%3A%2F%2Fevil.com — the shared LoginCard component echoes the value verbatim into the hidden returnUrl form field (the LoginCard.razor security comment explicitly states: "the consuming app's POST handler MUST validate it is a local/relative URL before redirecting to prevent open-redirect"). After a successful bind the endpoint issues Results.Redirect("https://evil.com"), silently forwarding the authenticated user to an attacker-controlled site.
The RedirectToLogin.razor Blazor component's own returnUrl generation is safe (Nav.ToBaseRelativePath always produces a relative path), but nothing prevents an attacker from directly constructing the URL. The normal cookie-auth challenge path also produces only relative paths. The gap is the absence of server-side local-URL validation on the consumed form field before redirecting.
Recommendation: Replace Results.Redirect(returnUrl) with a local-URL guard: use Uri.IsWellFormedUriString(returnUrl, UriKind.Relative) to validate the URL is relative before redirecting; fall back to "/" for absolute or malformed values. Results.LocalRedirect could alternatively be used but throws on invalid input (which the endpoint would need to catch).
Resolution: Fixed 2026-06-19. Added IsLocalUrl helper that rejects absolute and protocol-relative URLs; success-path now falls back to "/" on invalid input. Regression test Login_with_absolute_returnUrl_does_not_open_redirect added to AuthEndpointsIntegrationTests.
Security-002
| Field | Value |
|---|---|
| Severity | Low |
| Category | Documentation & comments |
| Location | src/Server/ZB.MOM.WW.OtOpcUa.Security/Ldap/LdapOptions.cs:9 |
| Status | Resolved |
Description: The XML doc comment on LdapOptions references C:\publish\glauth\auth.md as the source for dev LDAP defaults. Per CLAUDE.md (§ LDAP Authentication) the per-VM NSSM GLAuth at C:\publish\glauth\ is obsolete — the shared GLAuth now runs on the Linux Docker host at 10.100.0.35:3893. A developer following the stale reference would look for a file that no longer exists on the machine.
Recommendation: Update the doc comment to remove the stale local-path reference and direct readers to docs/security.md and CLAUDE.md §"LDAP Authentication" for dev LDAP setup.
Resolution: Fixed 2026-06-19. Stale C:\publish\glauth\auth.md reference replaced with pointer to docs/security.md.