From 80f29a7f546fb7ca3f6481e527f879f7420c6ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Tue, 10 May 2022 18:10:29 +0200 Subject: [PATCH] Update the documentation to clarify that state validation also helps mitigate authorization code injection attacks --- .../OpenIddictClientAspNetCoreHandlers.cs | 27 +++++++++++-------- .../OpenIddictClientAspNetCoreOptions.cs | 7 ++--- .../OpenIddictClientOwinHandlers.cs | 27 +++++++++++-------- .../OpenIddictClientOwinOptions.cs | 11 +++++--- ...ictClientSystemNetHttpHandlers.Userinfo.cs | 9 ++++++- .../OpenIddictClientSystemNetHttpHandlers.cs | 6 +++++ ...enIddictValidationSystemNetHttpHandlers.cs | 6 +++++ 7 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs index be02bb98..6320c475 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs @@ -215,7 +215,8 @@ public static partial class OpenIddictClientAspNetCoreHandlers } /// - /// Contains the logic responsible for validating the correlation cookie that serves as a CSRF protection. + /// Contains the logic responsible for validating the correlation cookie that serves as a + /// protection against state token injection, forged requests and session fixation attacks. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// public class ValidateCorrelationCookie : IOpenIddictClientHandler @@ -255,8 +256,8 @@ public static partial class OpenIddictClientAspNetCoreHandlers // Resolve the request forgery protection from the state token principal. // If the claim cannot be found, this means the protection was disabled // using a custom event handler. In this case, bypass the validation. - var claim = context.StateTokenPrincipal.GetClaim(Claims.RequestForgeryProtection); - if (string.IsNullOrEmpty(claim)) + var identifier = context.StateTokenPrincipal.GetClaim(Claims.RequestForgeryProtection); + if (string.IsNullOrEmpty(identifier)) { return default; } @@ -268,15 +269,19 @@ public static partial class OpenIddictClientAspNetCoreHandlers // and the random request forgery protection claim restored from the state. var name = new StringBuilder(builder.Name) .Append(Separators.Dot) - .Append(claim) + .Append(identifier) .ToString(); // Try to find the cookie matching the request forgery protection stored in the state. // // If the cookie cannot be found, this may indicate that the authorization response // is unsolicited and potentially malicious. This may also be caused by an unadequate - // same-site configuration. In any case, the authentication demand MUST be rejected - // as it's impossible to ensure it's not a session fixation attack without the cookie. + // same-site configuration. The correlation cookie also serves as a binding mechanism + // ensuring that a state token stolen from an authorization response with the other + // parameters cannot be validly used without sending the matching correlation identifier. + // + // In any case, the authentication demand MUST be rejected as it's impossible to ensure + // it's not an injection or session fixation attack without the correlation cookie. var value = request.Cookies[name]; if (string.IsNullOrEmpty(value) || !string.Equals(value, "v1", StringComparison.Ordinal)) { @@ -347,7 +352,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers // abort the authorization flow by rejecting the token request that may be eventually sent // with the original redirect_uri, many servers are known to incorrectly implement this // redirect_uri validation logic. This check also offers limited protection as it cannot - // prevent the client credentials from being leaked to a malicious authorization server. + // prevent the authorization code from being leaked to a malicious authorization server. // By comparing the redirect_uri directly in the client, a first layer of protection is // provided independently of whether the authorization server will enforce this check. // @@ -455,7 +460,8 @@ public static partial class OpenIddictClientAspNetCoreHandlers } /// - /// Contains the logic responsible for creating a correlation cookie that serves as a CSRF countermeasure. + /// Contains the logic responsible for creating a correlation cookie that serves as a + /// protection against state token injection, forged requests and session fixation attacks. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// public class GenerateCorrelationCookie : IOpenIddictClientHandler @@ -485,8 +491,8 @@ public static partial class OpenIddictClientAspNetCoreHandlers throw new ArgumentNullException(nameof(context)); } - // Note: using a correlation cookie serves as an antiforgery protection as the request will - // always be rejected if a cookie corresponding to the request forgery protection claim + // Note: using a correlation cookie serves as an injection/antiforgery protection as the request + // will always be rejected if a cookie corresponding to the request forgery protection claim // persisted in the state token cannot be found. This protection is considered essential // in OpenIddict and cannot be disabled via the options. Applications that prefer implementing // a different protection strategy can set the request forgery protection claim to null or @@ -498,7 +504,6 @@ public static partial class OpenIddictClientAspNetCoreHandlers } Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - Debug.Assert(!string.IsNullOrEmpty(context.StateToken), SR.GetResourceString(SR.ID4010)); // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, // this may indicate that the request was incorrectly processed by another server stack. diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreOptions.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreOptions.cs index 2e41147c..f11ec658 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreOptions.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreOptions.cs @@ -40,14 +40,15 @@ public class OpenIddictClientAspNetCoreOptions : AuthenticationSchemeOptions public bool EnableStatusCodePagesIntegration { get; set; } /// - /// Gets or sets the cookie builder used to create the cookies that are - /// used to protect against forged requests/session fixation attacks. + /// Gets or sets the cookie builder used to create the cookies that are used to + /// bind authorization responses with their original request and help mitigate + /// authorization code injection, forged requests and session fixation attacks. /// public CookieBuilder CookieBuilder { get; set; } = new() { HttpOnly = true, IsEssential = true, - Name = "OpenIddict.Client.RequestForgeryProtection", + Name = "OpenIddict.Client.State", SameSite = SameSiteMode.None, SecurePolicy = CookieSecurePolicy.Always // Note: same-site=none requires using HTTPS. }; diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs index 3a219a35..5b39ea4b 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs @@ -212,7 +212,8 @@ public static partial class OpenIddictClientOwinHandlers } /// - /// Contains the logic responsible for validating the correlation cookie that serves as a CSRF protection. + /// Contains the logic responsible for validating the correlation cookie that serves as a + /// protection against state token injection, forged requests and session fixation attacks. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// public class ValidateCorrelationCookie : IOpenIddictClientHandler @@ -252,8 +253,8 @@ public static partial class OpenIddictClientOwinHandlers // Resolve the request forgery protection from the state token principal. // If the claim cannot be found, this means the protection was disabled // using a custom event handler. In this case, bypass the validation. - var claim = context.StateTokenPrincipal.GetClaim(Claims.RequestForgeryProtection); - if (string.IsNullOrEmpty(claim)) + var identifier = context.StateTokenPrincipal.GetClaim(Claims.RequestForgeryProtection); + if (string.IsNullOrEmpty(identifier)) { return default; } @@ -267,15 +268,19 @@ public static partial class OpenIddictClientOwinHandlers // and the random request forgery protection claim restored from the state. var name = new StringBuilder(_options.CurrentValue.CookieName) .Append(Separators.Dot) - .Append(claim) + .Append(identifier) .ToString(); // Try to find the cookie matching the request forgery protection stored in the state. // // If the cookie cannot be found, this may indicate that the authorization response // is unsolicited and potentially malicious. This may also be caused by an unadequate - // same-site configuration. In any case, the authentication demand MUST be rejected - // as it's impossible to ensure it's not a session fixation attack without the cookie. + // same-site configuration. The correlation cookie also serves as a binding mechanism + // ensuring that a state token stolen from an authorization response with the other + // parameters cannot be validly used without sending the matching correlation identifier. + // + // In any case, the authentication demand MUST be rejected as it's impossible to ensure + // it's not an injection or session fixation attack without the correlation cookie. var value = manager.GetRequestCookie(request.Context, name); if (string.IsNullOrEmpty(value) || !string.Equals(value, "v1", StringComparison.Ordinal)) { @@ -353,7 +358,7 @@ public static partial class OpenIddictClientOwinHandlers // abort the authorization flow by rejecting the token request that may be eventually sent // with the original redirect_uri, many servers are known to incorrectly implement this // redirect_uri validation logic. This check also offers limited protection as it cannot - // prevent the client credentials from being leaked to a malicious authorization server. + // prevent the authorization code from being leaked to a malicious authorization server. // By comparing the redirect_uri directly in the client, a first layer of protection is // provided independently of whether the authorization server will enforce this check. // @@ -454,7 +459,8 @@ public static partial class OpenIddictClientOwinHandlers } /// - /// Contains the logic responsible for creating a correlation cookie that serves as a CSRF countermeasure. + /// Contains the logic responsible for creating a correlation cookie that serves as a + /// protection against state token injection, forged requests and session fixation attacks. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// public class GenerateCorrelationCookie : IOpenIddictClientHandler @@ -484,8 +490,8 @@ public static partial class OpenIddictClientOwinHandlers throw new ArgumentNullException(nameof(context)); } - // Note: using a correlation cookie serves as an antiforgery protection as the request will - // always be rejected if a cookie corresponding to the request forgery protection claim + // Note: using a correlation cookie serves as an injection/antiforgery protection as the request + // will always be rejected if a cookie corresponding to the request forgery protection claim // persisted in the state token cannot be found. This protection is considered essential // in OpenIddict and cannot be disabled via the options. Applications that prefer implementing // a different protection strategy can set the request forgery protection claim to null or @@ -497,7 +503,6 @@ public static partial class OpenIddictClientOwinHandlers } Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - Debug.Assert(!string.IsNullOrEmpty(context.StateToken), SR.GetResourceString(SR.ID4010)); // This handler only applies to OWIN requests. If the HTTP context cannot be resolved, // this may indicate that the request was incorrectly processed by another server stack. diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinOptions.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinOptions.cs index 2a553626..2fc93527 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinOptions.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinOptions.cs @@ -45,13 +45,16 @@ public class OpenIddictClientOwinOptions : AuthenticationOptions public ICookieManager CookieManager { get; set; } = new CookieManager(); /// - /// Gets or sets the name of the cookie used to protect against forged requests/session fixation attacks. + /// Gets or sets the name of the correlation cookie used to bind authorization + /// responses with their original request and help mitigate authorization + /// code injection, forged requests and session fixation attacks. /// - public string CookieName { get; set; } = "OpenIddict.Client.RequestForgeryProtection"; + public string CookieName { get; set; } = "OpenIddict.Client.State"; /// - /// Gets or sets the cookie options used to create the cookies that are - /// used to protect against forged requests/session fixation attacks. + /// Gets or sets the cookie options used to create the cookies that are used to + /// bind authorization responses with their original request and help mitigate + /// authorization code injection, forged requests and session fixation attacks. /// public CookieOptions CookieOptions { get; set; } = new() { diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs index 0e2d5e0f..61ead133 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs @@ -23,6 +23,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers */ PrepareGetHttpRequest.Descriptor, AttachBearerAccessToken.Descriptor, + AttachQueryStringParameters.Descriptor, SendHttpRequest.Descriptor, DisposeHttpRequest.Descriptor, @@ -44,7 +45,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(AttachFormParameters.Descriptor.Order - 1000) + .SetOrder(AttachQueryStringParameters.Descriptor.Order - 500) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -97,6 +98,12 @@ public static partial class OpenIddictClientSystemNetHttpHandlers throw new ArgumentNullException(nameof(context)); } + // Don't overwrite the response if one was already provided. + if (context.Response is not null || !string.IsNullOrEmpty(context.UserinfoToken)) + { + return; + } + // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, // this may indicate that the request was incorrectly processed by another client stack. var response = context.Transaction.GetHttpResponseMessage() ?? diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index 2cca67c0..b9920aa4 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -345,6 +345,12 @@ public static partial class OpenIddictClientSystemNetHttpHandlers throw new ArgumentNullException(nameof(context)); } + // Don't overwrite the response if one was already provided. + if (context.Transaction.Response is not null) + { + return; + } + // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, // this may indicate that the request was incorrectly processed by another client stack. var response = context.Transaction.GetHttpResponseMessage() ?? diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index 22db0ff8..9772e18e 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -344,6 +344,12 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers throw new ArgumentNullException(nameof(context)); } + // Don't overwrite the response if one was already provided. + if (context.Transaction.Response is not null) + { + return; + } + // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, // this may indicate that the request was incorrectly processed by another client stack. var response = context.Transaction.GetHttpResponseMessage() ??