diff --git a/src/Core/Auth/Identity/TokenProviders/DuoUniversalTokenService.cs b/src/Core/Auth/Identity/TokenProviders/DuoUniversalTokenService.cs index 2a299cbcd9..d7ac1e2510 100644 --- a/src/Core/Auth/Identity/TokenProviders/DuoUniversalTokenService.cs +++ b/src/Core/Auth/Identity/TokenProviders/DuoUniversalTokenService.cs @@ -7,6 +7,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Settings; using Bit.Core.Tokens; +using Microsoft.AspNetCore.Http; using Duo = DuoUniversal; namespace Bit.Core.Auth.Identity.TokenProviders; @@ -170,13 +171,51 @@ public class DuoUniversalTokenService( normalizedHost.EndsWith("bitwarden.pw"); } + private static bool IsLocalRequestHost(string host) + { + if (string.IsNullOrWhiteSpace(host)) + { + return false; + } + + var normalizedHost = host.ToLowerInvariant(); + return normalizedHost == "localhost" || + normalizedHost == "127.0.0.1" || + normalizedHost == "::1" || + normalizedHost.EndsWith(".localhost"); + } + + private static string GetDeeplinkSchemeOverride(HttpContext httpContext) + { + if (httpContext == null) + { + return null; + } + + var host = httpContext.Request?.Host.Host; + // Only allow overrides when developing/testing locally to avoid abuse in production + if (!IsLocalRequestHost(host)) + { + return null; + } + + // Querystring has precedence over header for manual local testing + var overrideFromQuery = httpContext.Request?.Query["deeplinkScheme"].FirstOrDefault(); + var overrideFromHeader = httpContext.Request?.Headers["Bitwarden-Deeplink-Scheme"].FirstOrDefault(); + var candidate = (overrideFromQuery ?? overrideFromHeader)?.Trim().ToLowerInvariant(); + + // Allow only the two supported values + return candidate is "https" or "bitwarden" ? candidate : null; + } + public async Task BuildDuoTwoFactorClientAsync(TwoFactorProvider provider) { // Fetch Client name from header value since duo auth can be initiated from multiple clients and we want // to redirect back to the initiating client _currentContext.HttpContext.Request.Headers.TryGetValue("Bitwarden-Client-Name", out var bitwardenClientName); var requestHost = _currentContext.HttpContext?.Request?.Host.Host; - var deeplinkScheme = IsBitwardenCloudHost(requestHost) ? "https" : "bitwarden"; + var deeplinkScheme = GetDeeplinkSchemeOverride(_currentContext.HttpContext) ?? + (IsBitwardenCloudHost(requestHost) ? "https" : "bitwarden"); var redirectUri = string.Format("{0}/duo-redirect-connector.html?client={1}&deeplinkScheme={2}", _globalSettings.BaseServiceUri.Vault, bitwardenClientName.FirstOrDefault() ?? "web", deeplinkScheme);