Skip to content

Commit 25a42f3

Browse files
daniel-beckjenkinsci-cert-ci
authored andcommitted
1 parent 13a4a94 commit 25a42f3

3 files changed

Lines changed: 137 additions & 8 deletions

File tree

core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import javax.servlet.http.HttpSession;
3535
import jenkins.security.SecurityListener;
3636
import jenkins.security.seed.UserSeedProperty;
37+
import jenkins.util.SystemProperties;
3738
import org.kohsuke.accmod.Restricted;
3839
import org.kohsuke.accmod.restrictions.NoExternalUse;
3940
import org.springframework.security.core.Authentication;
@@ -60,15 +61,13 @@ public AuthenticationProcessingFilter2(String authenticationGatewayUrl) {
6061

6162
@Override
6263
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
64+
if (SystemProperties.getInteger(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", 1) == 2) {
65+
// This is the default session fixation prevention fix.
66+
// While use of SessionFixationProtectionStrategy would be the canonical Spring Security approach, it seems less compatible with some security realms.
67+
request.getSession().invalidate();
68+
HttpSession newSession = request.getSession(true);
69+
}
6370
super.successfulAuthentication(request, response, chain, authResult);
64-
// make sure we have a session to store this successful authentication, given that we no longer
65-
// let HttpSessionContextIntegrationFilter2 to create sessions.
66-
// SecurityContextPersistenceFilter stores the updated SecurityContext object into this session later
67-
// (either when a redirect is issued, via its HttpResponseWrapper, or when the execution returns to its
68-
// doFilter method.
69-
/* TODO causes an ISE on the next line:
70-
request.getSession().invalidate();
71-
*/
7271
HttpSession newSession = request.getSession();
7372

7473
if (!UserSeedProperty.DISABLE_USER_SEED) {

core/src/main/java/hudson/security/SecurityRealm.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import javax.servlet.http.HttpSession;
5050
import jenkins.model.IdStrategy;
5151
import jenkins.model.Jenkins;
52+
import jenkins.util.SystemProperties;
5253
import jenkins.security.AcegiSecurityExceptionFilter;
5354
import jenkins.security.BasicHeaderProcessor;
5455
import jenkins.security.AuthenticationSuccessHandler;
@@ -78,6 +79,7 @@
7879
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
7980
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
8081
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
82+
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
8183
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
8284

8385
/**
@@ -593,6 +595,10 @@ public Filter createFilter(FilterConfig filterConfig) {
593595
{
594596
AuthenticationProcessingFilter2 apf = new AuthenticationProcessingFilter2(getAuthenticationGatewayUrl());
595597
apf.setAuthenticationManager(sc.manager2);
598+
if (SystemProperties.getInteger(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", 1) == 1) {
599+
// Optionally use the 'canonical' protection from Spring Security; see AuthenticationProcessingFilter2#successfulAuthentication for default
600+
apf.setSessionAuthenticationStrategy(new SessionFixationProtectionStrategy());
601+
}
596602
apf.setRememberMeServices(sc.rememberMe2);
597603
final AuthenticationSuccessHandler successHandler = new AuthenticationSuccessHandler();
598604
successHandler.setTargetUrlParameter("from");
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2021 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package hudson.security;
25+
26+
import com.gargoylesoftware.htmlunit.util.Cookie;
27+
import jenkins.model.Jenkins;
28+
import org.junit.Assert;
29+
import org.junit.Rule;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.junit.runners.Parameterized;
33+
import org.jvnet.hudson.test.JenkinsRule;
34+
import org.jvnet.hudson.test.MockAuthorizationStrategy;
35+
36+
import java.util.Arrays;
37+
import java.util.List;
38+
39+
/**
40+
* Split from {@link SecurityRealmTest} because this is parameterized.
41+
*/
42+
@RunWith(Parameterized.class)
43+
public class SecurityRealmSecurity2371Test {
44+
45+
public static final String SESSION_COOKIE_NAME = "JSESSIONID";
46+
public static final String USERNAME = "alice";
47+
48+
private final Integer mode;
49+
50+
@Rule
51+
public JenkinsRule j = new JenkinsRule();
52+
53+
@Parameterized.Parameters
54+
public static List<Integer> modes() {
55+
return Arrays.asList(null, 1, 2);
56+
}
57+
58+
public SecurityRealmSecurity2371Test(Integer mode) {
59+
this.mode = mode;
60+
}
61+
62+
@Test
63+
public void testSessionChangeOnLogin() throws Exception {
64+
if (mode != null) {
65+
System.setProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", String.valueOf(mode));
66+
}
67+
try {
68+
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
69+
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.READ).everywhere().toEveryone().grant(Jenkins.ADMINISTER).everywhere().to(USERNAME));
70+
final JenkinsRule.WebClient webClient = j.createWebClient();
71+
webClient.goTo("");
72+
try {
73+
webClient.goTo("manage");
74+
Assert.fail("anonymous session should not be able to go to /manage");
75+
} catch (Exception ex) {
76+
// OK
77+
}
78+
final Cookie anonymousCookie = webClient.getCookieManager().getCookie(SESSION_COOKIE_NAME); // dynamic cookie names are only set when run through Winstone
79+
webClient.login(USERNAME);
80+
webClient.goTo("");
81+
final Cookie aliceCookie = webClient.getCookieManager().getCookie(SESSION_COOKIE_NAME);
82+
83+
// Confirm the session cookie changed
84+
// We cannot just call #assertNotEquals(Cookie, Cookie) because it doesn't actually look at #getValue()
85+
Assert.assertNotEquals(anonymousCookie.getValue(), aliceCookie.getValue());
86+
87+
// Now ensure the old session was actually invalidated / is not associated with the new auth
88+
webClient.getCookieManager().clearCookies();
89+
webClient.getCookieManager().addCookie(anonymousCookie);
90+
try {
91+
webClient.goTo("manage");
92+
Assert.fail("anonymous session should not be able to go to /manage");
93+
} catch (Exception ex) {
94+
// OK
95+
}
96+
} finally {
97+
System.clearProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode");
98+
}
99+
}
100+
101+
/**
102+
* Explicitly disable
103+
*/
104+
@Test
105+
public void optOut() throws Exception {
106+
System.setProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", String.valueOf(0));
107+
try {
108+
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
109+
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.READ).everywhere().toEveryone().grant(Jenkins.ADMINISTER).everywhere().to(USERNAME));
110+
final JenkinsRule.WebClient webClient = j.createWebClient();
111+
webClient.goTo("");
112+
113+
final Cookie anonymousCookie = webClient.getCookieManager().getCookie(SESSION_COOKIE_NAME); // dynamic cookie names are only set when run through Winstone
114+
webClient.login(USERNAME);
115+
webClient.goTo("");
116+
final Cookie aliceCookie = webClient.getCookieManager().getCookie(SESSION_COOKIE_NAME);
117+
118+
// Confirm the session cookie did not change
119+
Assert.assertEquals(anonymousCookie.getValue(), aliceCookie.getValue());
120+
} finally {
121+
System.clearProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode");
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)