@@ -33,25 +33,29 @@ public async Task Login_WithoutCsrfToken_MustNotCreateSession()
3333 public async Task Login_WithForgedCsrfToken_MustNotCreateSession ( )
3434 {
3535 await using var factory = new AuthServerFactory ( ) ;
36- var attacker = await factory . CreateLoginUserAsync ( ct : TestContext . Current . CancellationToken ) ;
36+ var ct = TestContext . Current . CancellationToken ;
37+
38+ var user = await factory . CreateLoginUserAsync ( ct : ct ) ;
3739
3840 using var victimBrowser = CreateBrowser ( factory ) ;
3941
40- using var request = new HttpRequestMessage (
41- HttpMethod . Post , "/auth/login" )
42+ // Obtain a real antiforgery cookie and request token.
43+ var tokens = await GetTokens ( victimBrowser ) ;
44+
45+ using var request = new HttpRequestMessage ( HttpMethod . Post , "/auth/login" )
4246 {
4347 Content = JsonContent . Create ( new
4448 {
45- identifier = attacker . Identifier ,
46- secret = attacker . Secret
49+ identifier = user . Identifier ,
50+ secret = user . Secret
4751 } )
4852 } ;
4953
50- request . Headers . TryAddWithoutValidation (
51- "RequestVerificationToken" ,
52- "attacker-forged-token" ) ;
54+ // Keep the legitimate cookie, but forge the request token.
55+ request . Headers . TryAddWithoutValidation ( tokens . HeaderName , "attacker-forged-token" ) ;
56+ request . Headers . TryAddWithoutValidation ( "Cookie" , tokens . Cookie ) ;
5357
54- using var response = await victimBrowser . SendAsync ( request , TestContext . Current . CancellationToken ) ;
58+ using var response = await victimBrowser . SendAsync ( request , ct ) ;
5559
5660 await AssertCsrfRejected ( response ) ;
5761 AssertNoSessionCookie ( response ) ;
@@ -167,28 +171,26 @@ public async Task RevokeOthers_CsrfAttack_MustNotRevokeSessions()
167171 // The attack must not revoke the other session.
168172 await AssertAuthenticated ( otherBrowser ) ;
169173
170- // ATTACK 2: Forged antiforgery token.
171- using ( var forgedRequest = new HttpRequestMessage (
172- HttpMethod . Post ,
173- "/auth/me/sessions/revoke-others" ) )
174+ // ATTACK 2: Valid antiforgery cookie with a forged request token.
175+ var forgedTokens = await GetTokens ( currentBrowser ) ;
176+
177+ using ( var forgedRequest = new HttpRequestMessage ( HttpMethod . Post , "/auth/me/sessions/revoke-others" ) )
174178 {
175- forgedRequest . Headers . TryAddWithoutValidation (
176- "RequestVerificationToken" ,
177- "forged-token" ) ;
179+ forgedRequest . Headers . TryAddWithoutValidation ( forgedTokens . HeaderName , "attacker-forged-token" ) ;
178180
179- using var forged = await currentBrowser . SendAsync (
180- forgedRequest , TestContext . Current . CancellationToken ) ;
181+ // Preserve the authenticated session cookie and
182+ // include the legitimate antiforgery cookie.
183+ forgedRequest . Headers . TryAddWithoutValidation ( "Cookie" , $ "{ currentCookie } ; { forgedTokens . Cookie } ") ;
181184
185+ using var forged = await currentBrowser . SendAsync ( forgedRequest , TestContext . Current . CancellationToken ) ;
182186 await AssertCsrfRejected ( forged ) ;
183187 }
184188
185- // The second attack must not revoke the other session.
189+ // A rejected CSRF request must not revoke the other session.
186190 await AssertAuthenticated ( otherBrowser ) ;
187191
188192 // CONTROL: Valid CSRF token must allow the operation.
189- using ( var valid = await CsrfTestClient . PostAsync (
190- currentBrowser ,
191- "/auth/me/sessions/revoke-others" ) )
193+ using ( var valid = await CsrfTestClient . PostAsync ( currentBrowser , "/auth/me/sessions/revoke-others" ) )
192194 {
193195 valid . IsSuccessStatusCode . Should ( ) . BeTrue (
194196 $ "valid revoke failed: " +
@@ -199,15 +201,12 @@ public async Task RevokeOthers_CsrfAttack_MustNotRevokeSessions()
199201 await AssertAuthenticated ( currentBrowser ) ;
200202
201203 // The other session must now be revoked.
202- using var revokedCheck = await CsrfTestClient . PostJsonAsync (
203- otherBrowser ,
204- "/auth/me/identifiers/get" ,
205- new { } ) ;
204+ using var revokedCheck = await CsrfTestClient . PostJsonAsync ( otherBrowser , "/auth/me/identifiers/get" , new { } ) ;
206205
207- revokedCheck . StatusCode . Should ( ) . Be (
208- HttpStatusCode . Unauthorized ) ;
206+ revokedCheck . StatusCode . Should ( ) . Be ( HttpStatusCode . Unauthorized ) ;
209207 }
210208
209+
211210 private static async Task < string > LoginAndGetSessionCookie (
212211 HttpClient browser ,
213212 IntegrationTestUser user )
0 commit comments