Summary
When user.security.appUrl is not configured, AppUrlResolver derives the authority for outbound email links (password reset and registration verification) from the incoming request without a complete allowlist check. On common servlet containers this authority is influenced by the Host header, so the links embedded in recovery/verification emails can point at a host the operator did not intend.
Severity: High (insecure-by-default; fully mitigated by setting user.security.appUrl)
Affected code
src/main/java/com/digitalsanctuary/spring/user/util/AppUrlResolver.java:73 — the non-forwarded fallback uses request.getServerName() with no trustedHosts validation, whereas X-Forwarded-Host is validated against trustedHosts.
src/main/java/com/digitalsanctuary/spring/user/service/UserEmailService.java:182 — the emailed link is built from the resolved app URL.
src/main/resources/config/dsspringuserconfig.properties:83,85 — appUrl and trustedHosts both ship empty.
/user/resetPassword and /user/registration are unauthenticated by default.
Details
resolveAppUrl() short-circuits and returns appUrl when it is configured. When it is blank, it falls through to request-derived resolution: a forwarded host is only used if it passes the trustedHosts allowlist, but the ordinary server name path applies no such check. On stock Tomcat/Jetty/Undertow, getServerName() reflects the Host header for unrecognized hosts, so the resulting link authority is externally influenced.
Impact
A single-use bearer token delivered in a verification/reset email could be rendered into a link whose host is not the canonical application host, undermining the recovery mechanism (CWE-640).
Recommended fix
- Require a canonical
user.security.appUrl whenever recovery/verification email is enabled; prefer failing startup if it is absent.
- Validate it as absolute HTTPS in production (no user-info, query, or fragment).
- If request-derived fallback must remain, apply the same allowlist to the ordinary host as to the forwarded host.
AppUrlResolverTest currently encodes the permissive fallback (accepts an arbitrary non-forwarded server name); update alongside the fix.
Identified during a code-first security assessment of the current main branch. Reported without an exploit recipe per public-repo disclosure hygiene.
Summary
When
user.security.appUrlis not configured,AppUrlResolverderives the authority for outbound email links (password reset and registration verification) from the incoming request without a complete allowlist check. On common servlet containers this authority is influenced by theHostheader, so the links embedded in recovery/verification emails can point at a host the operator did not intend.Severity: High (insecure-by-default; fully mitigated by setting
user.security.appUrl)Affected code
src/main/java/com/digitalsanctuary/spring/user/util/AppUrlResolver.java:73— the non-forwarded fallback usesrequest.getServerName()with notrustedHostsvalidation, whereasX-Forwarded-Hostis validated againsttrustedHosts.src/main/java/com/digitalsanctuary/spring/user/service/UserEmailService.java:182— the emailed link is built from the resolved app URL.src/main/resources/config/dsspringuserconfig.properties:83,85—appUrlandtrustedHostsboth ship empty./user/resetPasswordand/user/registrationare unauthenticated by default.Details
resolveAppUrl()short-circuits and returnsappUrlwhen it is configured. When it is blank, it falls through to request-derived resolution: a forwarded host is only used if it passes thetrustedHostsallowlist, but the ordinary server name path applies no such check. On stock Tomcat/Jetty/Undertow,getServerName()reflects theHostheader for unrecognized hosts, so the resulting link authority is externally influenced.Impact
A single-use bearer token delivered in a verification/reset email could be rendered into a link whose host is not the canonical application host, undermining the recovery mechanism (CWE-640).
Recommended fix
user.security.appUrlwhenever recovery/verification email is enabled; prefer failing startup if it is absent.AppUrlResolverTestcurrently encodes the permissive fallback (accepts an arbitrary non-forwarded server name); update alongside the fix.Identified during a code-first security assessment of the current
mainbranch. Reported without an exploit recipe per public-repo disclosure hygiene.