1010import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
1111import hudson .Extension ;
1212import hudson .Util ;
13+ import hudson .model .AdministrativeMonitor ;
1314import hudson .model .ModelObject ;
1415import hudson .model .PersistentDescriptor ;
16+ import jakarta .servlet .ServletException ;
1517import jakarta .servlet .ServletRequest ;
1618import jakarta .servlet .http .HttpServletRequest ;
19+ import java .io .IOException ;
1720import java .nio .charset .StandardCharsets ;
1821import java .security .MessageDigest ;
1922import java .security .NoSuchAlgorithmException ;
2730import org .kohsuke .accmod .Restricted ;
2831import org .kohsuke .accmod .restrictions .NoExternalUse ;
2932import org .kohsuke .stapler .DataBoundConstructor ;
33+ import org .kohsuke .stapler .QueryParameter ;
3034import org .kohsuke .stapler .StaplerRequest2 ;
35+ import org .kohsuke .stapler .StaplerResponse2 ;
36+ import org .kohsuke .stapler .verb .POST ;
3137import org .springframework .security .core .Authentication ;
3238
3339/**
34- * A crumb issuing algorithm based on the request principal and the remote address .
40+ * A crumb issuing algorithm based on the request principal and the session ID .
3541 *
3642 * @author dty
3743 */
3844public class DefaultCrumbIssuer extends CrumbIssuer {
3945
4046 private transient MessageDigest md ;
41- private boolean excludeClientIPFromCrumb ;
47+ private transient boolean excludeClientIPFromCrumb ;
4248
4349 @ Restricted (NoExternalUse .class )
4450 @ SuppressFBWarnings (value = "MS_SHOULD_BE_FINAL" , justification = "for script console" )
4551 public static /* non-final: Groovy Console */ boolean EXCLUDE_SESSION_ID = SystemProperties .getBoolean (DefaultCrumbIssuer .class .getName () + ".EXCLUDE_SESSION_ID" );
4652
4753 @ DataBoundConstructor
54+ public DefaultCrumbIssuer () {
55+ initializeMessageDigest ();
56+ logSessionIdWarningIfNeeded ();
57+ }
58+
59+ /**
60+ * @param excludeClientIPFromCrumb unused
61+ * @deprecated Use {@link #DefaultCrumbIssuer()} instead.
62+ */
63+ @ Deprecated
4864 public DefaultCrumbIssuer (boolean excludeClientIPFromCrumb ) {
65+ this ();
4966 this .excludeClientIPFromCrumb = excludeClientIPFromCrumb ;
50- initializeMessageDigest ();
67+ logSessionIdWarningIfNeeded ();
5168 }
5269
70+ private void logSessionIdWarningIfNeeded () {
71+ if (EXCLUDE_SESSION_ID ) {
72+ LOGGER .warning ("Jenkins no longer uses the client IP address as part of CSRF protection. " +
73+ "This controller is configured to also not use the session ID (hudson.security.csrf.DefaultCrumbIssuer.EXCLUDE_SESSION_ID), which is even less safe now. " +
74+ "This option will be removed in future releases." );
75+ }
76+ }
77+
78+ /**
79+ * @return the previously set value
80+ * @deprecated This setting is no longer effective.
81+ */
82+ @ Deprecated
5383 public boolean isExcludeClientIPFromCrumb () {
5484 return this .excludeClientIPFromCrumb ;
5585 }
@@ -77,10 +107,6 @@ protected synchronized String issueCrumb(ServletRequest request, String salt) {
77107 StringBuilder buffer = new StringBuilder ();
78108 Authentication a = Jenkins .getAuthentication2 ();
79109 buffer .append (a .getName ());
80- buffer .append (';' );
81- if (!isExcludeClientIPFromCrumb ()) {
82- buffer .append (getClientIP (req ));
83- }
84110 if (!EXCLUDE_SESSION_ID ) {
85111 buffer .append (';' );
86112 buffer .append (req .getSession ().getId ());
@@ -106,20 +132,6 @@ public boolean validateCrumb(ServletRequest request, String salt, String crumb)
106132 return false ;
107133 }
108134
109- private static final String X_FORWARDED_FOR = "X-Forwarded-For" ;
110-
111- private String getClientIP (HttpServletRequest req ) {
112- String defaultAddress = req .getRemoteAddr ();
113- String forwarded = req .getHeader (X_FORWARDED_FOR );
114- if (forwarded != null ) {
115- String [] hopList = forwarded .split ("," );
116- if (hopList .length >= 1 ) {
117- return hopList [0 ];
118- }
119- }
120- return defaultAddress ;
121- }
122-
123135 @ Extension @ Symbol ("standard" )
124136 public static final class DescriptorImpl extends CrumbIssuerDescriptor <DefaultCrumbIssuer > implements ModelObject , PersistentDescriptor {
125137
@@ -146,5 +158,41 @@ public DefaultCrumbIssuer newInstance(StaplerRequest2 req, JSONObject formData)
146158 }
147159 }
148160
161+ @ Extension
162+ @ Restricted (NoExternalUse .class )
163+ public static class ExcludeSessionIdAdministrativeMonitor extends AdministrativeMonitor {
164+
165+ @ Override
166+ public boolean isActivated () {
167+ final CrumbIssuer crumbIssuer = Jenkins .get ().getCrumbIssuer ();
168+ if (crumbIssuer instanceof DefaultCrumbIssuer ) {
169+ return EXCLUDE_SESSION_ID ;
170+ }
171+ return false ;
172+ }
173+
174+ @ Override
175+ public boolean isSecurity () {
176+ return true ;
177+ }
178+
179+ @ Override
180+ public String getDisplayName () {
181+ return "Warn when session ID is excluded from Default Crumb Issuer" ; // TODO i18n
182+ }
183+
184+ @ POST
185+ public void doAct (StaplerRequest2 req , StaplerResponse2 rsp , @ QueryParameter String learn , @ QueryParameter String dismiss ) throws IOException , ServletException {
186+ if (learn != null ) {
187+ rsp .sendRedirect ("https://www.jenkins.io/redirect/csrf-protection/" );
188+ return ;
189+ }
190+ if (dismiss != null ) {
191+ disable (true );
192+ }
193+ rsp .forwardToPreviousPage (req );
194+ }
195+ }
196+
149197 private static final Logger LOGGER = Logger .getLogger (DefaultCrumbIssuer .class .getName ());
150198}
0 commit comments