Skip to content

Commit fca6b87

Browse files
authored
URL redirect and ValidateIssuer CodeQL bugs (#9398)
* added Url redirect codeql suppressions * codeQL bug = suppression :) * moved suppression comment closer to the relevant line of code * We are restricting the reCAPTCHA request to a specific host. Changed the suppression comment to reflect this as the mitigation for the vulnerability. * Only allow relative Urls as a return Url. Add/adapt tests. * removed unnecessary usings * modified suppression comment for recaptcha redirection
1 parent ebd782b commit fca6b87

4 files changed

Lines changed: 57 additions & 12 deletions

File tree

src/NuGetGallery.Services/Authentication/Providers/AzureActiveDirectoryV2/AzureActiveDirectoryV2Authenticator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ protected override void AttachToOwinApp(IGalleryConfigurationService config, IAp
102102
PostLogoutRedirectUri = siteRoot,
103103
Scope = OpenIdConnectScope.OpenIdProfile + " email",
104104
ResponseType = OpenIdConnectResponseType.IdToken,
105+
// CodeQL [SM03926] We do not restrict issuers to a limited set of tenants for our multi-tenant app
105106
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = false },
106107
Notifications = new OpenIdConnectAuthenticationNotifications
107108
{

src/NuGetGallery/Controllers/AuthenticationController.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,12 @@ public virtual ActionResult AuthenticateExternal(string returnUrl)
445445
.SecurityPolicies
446446
.Any(policy => policy.Name == nameof(RequireOrganizationTenantPolicy)));
447447

448+
// Validate that the returnUrl is a relative URL to prevent untrusted URL redirection
449+
if (!Url.IsLocalUrl(returnUrl))
450+
{
451+
returnUrl = "/";
452+
}
453+
448454
if (userOrganizationsWithTenantPolicy != null && userOrganizationsWithTenantPolicy.Any())
449455
{
450456
var aadCredential = user?.Credentials.GetAzureActiveDirectoryCredential();

src/NuGetGallery/Filters/ValidateRecaptchaResponseAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ private async Task<bool> RecaptchaIsValid(string privateKey, string response)
4949

5050
try
5151
{
52+
// CodeQL [SM03781] The validation Url is restricted to a specific host, which mitigates the risk of unwanted redirection
5253
var reply = await Client.Value.GetStringAsync(validationUrl);
5354
var state = JsonConvert.DeserializeObject<RecaptchaState>(reply);
5455
return state.Success;

tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,23 +1576,27 @@ public async Task GivenEnableMultiFactorAuthenticationInUserIsFalseAndLoginUsedM
15761576

15771577
public class TheAuthenticateExternalAction : TestContainer
15781578
{
1579-
[Fact]
1580-
public void ForMissingExternalProvider_ErrorIsReturned()
1579+
[Theory]
1580+
[InlineData("/theReturnUrl")]
1581+
[InlineData("/")]
1582+
public void ForMissingExternalProvider_ErrorIsReturned(string returnUrl)
15811583
{
15821584
// Arrange
15831585
var controller = GetController<AuthenticationController>();
15841586
controller.SetCurrentUser(TestUtility.FakeUser);
15851587

15861588
// Act
1587-
var result = controller.AuthenticateExternal("theReturnUrl");
1589+
var result = controller.AuthenticateExternal(returnUrl);
15881590

15891591
// Assert
1590-
ResultAssert.IsRedirectTo(result, "theReturnUrl");
1592+
ResultAssert.IsRedirectTo(result, returnUrl);
15911593
Assert.Equal(Strings.ChangeCredential_ProviderNotFound, controller.TempData["Message"]);
15921594
}
15931595

1594-
[Fact]
1595-
public void ForAADLinkedAccount_ErrorIsReturnedDueToOrgPolicy()
1596+
[Theory]
1597+
[InlineData("/theReturnUrl")]
1598+
[InlineData("/")]
1599+
public void ForAADLinkedAccount_ErrorIsReturnedDueToOrgPolicy(string returnUrl)
15961600
{
15971601
// Arrange
15981602
var fakes = Get<Fakes>();
@@ -1612,15 +1616,17 @@ public void ForAADLinkedAccount_ErrorIsReturnedDueToOrgPolicy()
16121616
controller.SetCurrentUser(user);
16131617

16141618
// Act
1615-
var result = controller.AuthenticateExternal("theReturnUrl");
1619+
var result = controller.AuthenticateExternal(returnUrl);
16161620

16171621
// Assert
1618-
ResultAssert.IsRedirectTo(result, "theReturnUrl");
1622+
ResultAssert.IsRedirectTo(result, returnUrl);
16191623
Assert.NotNull(controller.TempData["WarningMessage"]);
16201624
}
16211625

1622-
[Fact]
1623-
public void ForNonAADLinkedAccount_WithOrgPolicyCompletesSuccessfully()
1626+
[Theory]
1627+
[InlineData("/theReturnUrl")]
1628+
[InlineData("/")]
1629+
public void ForNonAADLinkedAccount_WithOrgPolicyCompletesSuccessfully(string returnUrl)
16241630
{
16251631
// Arrange
16261632
var fakes = Get<Fakes>();
@@ -1640,11 +1646,11 @@ public void ForNonAADLinkedAccount_WithOrgPolicyCompletesSuccessfully()
16401646
controller.SetCurrentUser(user);
16411647

16421648
// Act
1643-
var result = controller.AuthenticateExternal("theReturnUrl");
1649+
var result = controller.AuthenticateExternal(returnUrl);
16441650

16451651
// Assert
16461652
Assert.Null(controller.TempData["WarningMessage"]);
1647-
ResultAssert.IsChallengeResult(result, "AzureActiveDirectoryV2", controller.Url.LinkOrChangeExternalCredential("theReturnUrl"));
1653+
ResultAssert.IsChallengeResult(result, "AzureActiveDirectoryV2", controller.Url.LinkOrChangeExternalCredential(returnUrl));
16481654
}
16491655

16501656
[Theory]
@@ -1685,6 +1691,37 @@ public void WillCallChallengeAuthenticationForAADv2Provider()
16851691
// Assert
16861692
ResultAssert.IsChallengeResult(result, "AzureActiveDirectoryV2", controller.Url.LinkOrChangeExternalCredential(returnUrl));
16871693
}
1694+
1695+
[Fact]
1696+
public void WillRedirectToHomePageOnFailureForAbsoluteUrls()
1697+
{
1698+
// Arrange
1699+
var controller = GetController<AuthenticationController>();
1700+
controller.SetCurrentUser(TestUtility.FakeUser);
1701+
1702+
// Act
1703+
var result = controller.AuthenticateExternal("theReturnUrl"); // not a relative URL
1704+
1705+
// Assert
1706+
ResultAssert.IsRedirectTo(result, "/");
1707+
Assert.Equal(Strings.ChangeCredential_ProviderNotFound, controller.TempData["Message"]);
1708+
}
1709+
1710+
[Fact]
1711+
public void WillRedirectToHomePageOnSuccessForAbsoluteUrls()
1712+
{
1713+
// Arrange
1714+
const string returnUrl = "theReturnUrl"; // not a relative URL
1715+
EnableAllAuthenticators(Get<AuthenticationService>());
1716+
var controller = GetController<AuthenticationController>();
1717+
controller.SetCurrentUser(TestUtility.FakeUser);
1718+
1719+
// Act
1720+
var result = controller.AuthenticateExternal(returnUrl);
1721+
1722+
// Assert
1723+
ResultAssert.IsChallengeResult(result, "AzureActiveDirectoryV2", controller.Url.LinkOrChangeExternalCredential("/"));
1724+
}
16881725
}
16891726

16901727
public class TheLinkExternalAccountAction : TestContainer

0 commit comments

Comments
 (0)