review(Security): fix login open-redirect (High) + stale LDAP doc
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.
This commit is contained in:
@@ -453,6 +453,40 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
resp.Headers.Location.OriginalString.ShouldContain("ReturnUrl");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Security-001 regression: a successful login with an absolute <c>returnUrl</c> must NOT
|
||||
/// redirect to the external URL. An attacker can craft
|
||||
/// <c>/login?returnUrl=https://evil.com</c>; the <see cref="LoginCard"/> component echoes
|
||||
/// the value into the hidden form field verbatim. The <c>/auth/login</c> endpoint must
|
||||
/// validate the URL is local before redirecting; it must fall back to <c>"/"</c> (the app
|
||||
/// root) rather than forwarding to an external host.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Login_with_absolute_returnUrl_does_not_open_redirect()
|
||||
{
|
||||
var client = NewClientNoRedirect();
|
||||
|
||||
// POST to /auth/login with valid credentials AND an absolute returnUrl.
|
||||
var formContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["username"] = "alice",
|
||||
["password"] = "valid-password",
|
||||
["returnUrl"] = "https://evil.com/steal-session",
|
||||
});
|
||||
var resp = await client.PostAsync("/auth/login", formContent, Ct);
|
||||
|
||||
// The endpoint must redirect (302), but NOT to the external URL.
|
||||
resp.StatusCode.ShouldBe(HttpStatusCode.Found,
|
||||
"a successful form-POST login must always redirect");
|
||||
|
||||
var location = resp.Headers.Location?.OriginalString ?? string.Empty;
|
||||
location.Contains("evil.com").ShouldBeFalse(
|
||||
"a successful login must not redirect to an externally-supplied absolute URL");
|
||||
|
||||
// The fallback destination must be the app root — not an error page.
|
||||
location.ShouldBe("/", "the safe fallback for an invalid returnUrl is the app root");
|
||||
}
|
||||
|
||||
/// <summary>Anonymous XHR GET of a protected route returns 401 (caller signaled non-browser
|
||||
/// via the <c>X-Requested-With</c> header — the ASP.NET cookie handler's IsAjaxRequest
|
||||
/// heuristic). The framework still writes a <c>Location</c> header alongside the 401;
|
||||
|
||||
Reference in New Issue
Block a user