Skip to content

Commit f31d592

Browse files
Merge branch 'master' into jakarta
# Conflicts: # csrfguard-extensions/csrfguard-extension-session/pom.xml # csrfguard-extensions/csrfguard-jsp-tags/pom.xml # csrfguard-extensions/pom.xml # csrfguard-test/csrfguard-test-jsp/pom.xml # csrfguard-test/pom.xml # csrfguard/pom.xml # pom.xml
2 parents 574d577 + 3da2a3c commit f31d592

File tree

14 files changed

+143
-47
lines changed

14 files changed

+143
-47
lines changed

csrfguard-extensions/csrfguard-extension-session/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@
5353
<artifactId>jakarta.servlet-api</artifactId>
5454
</dependency>
5555
</dependencies>
56-
</project>
56+
</project>

csrfguard-extensions/csrfguard-jsp-tags/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@
6969
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
7070
</dependency>
7171
</dependencies>
72-
</project>
72+
</project>

csrfguard/src/main/java/org/owasp/csrfguard/CsrfGuard.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ public String getJavascriptCacheControl() {
215215
return config().getJavascriptCacheControl();
216216
}
217217

218+
public String getJavascriptTaggedCacheControl() {
219+
return config().getJavascriptTaggedCacheControl();
220+
}
221+
218222
public Pattern getJavascriptRefererPattern() {
219223
return config().getJavascriptRefererPattern();
220224
}

csrfguard/src/main/java/org/owasp/csrfguard/config/ConfigurationProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ public interface ConfigurationProvider {
183183
*/
184184
String getJavascriptCacheControl();
185185

186+
/**
187+
* @return the configured JavaScript cache control when queried using a tag query param
188+
*/
189+
String getJavascriptTaggedCacheControl();
190+
186191
/**
187192
* @return the configured JavaScript "Referer" pattern to be used
188193
*/

csrfguard/src/main/java/org/owasp/csrfguard/config/NullConfigurationProvider.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ public String getJavascriptCacheControl() {
173173
return null;
174174
}
175175

176+
@Override public String getJavascriptTaggedCacheControl() {
177+
return null;
178+
}
179+
176180
@Override
177181
public Pattern getJavascriptRefererPattern() {
178182
return null;

csrfguard/src/main/java/org/owasp/csrfguard/config/PropertiesConfigurationProvider.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ public class PropertiesConfigurationProvider implements ConfigurationProvider {
117117

118118
private String javascriptCacheControl;
119119

120+
private String javascriptTaggedCacheControl;
121+
120122
private Pattern javascriptRefererPattern;
121123

122124
private boolean javascriptInjectIntoForms;
@@ -304,6 +306,10 @@ public String getJavascriptCacheControl() {
304306
return this.javascriptCacheControl;
305307
}
306308

309+
@Override public String getJavascriptTaggedCacheControl() {
310+
return this.javascriptTaggedCacheControl;
311+
}
312+
307313
@Override
308314
public Pattern getJavascriptRefererPattern() {
309315
return this.javascriptRefererPattern;
@@ -548,6 +554,7 @@ private void javascriptInitParamsIfNeeded() {
548554

549555
if (servletConfig != null) {
550556
this.javascriptCacheControl = getProperty(JavaScriptConfigParameters.CACHE_CONTROL, servletConfig);
557+
this.javascriptTaggedCacheControl = getProperty(JavaScriptConfigParameters.CACHE_CONTROL_TAGGED, servletConfig);
551558
this.javascriptDomainStrict = getProperty(JavaScriptConfigParameters.DOMAIN_STRICT, servletConfig);
552559
this.javascriptInjectIntoAttributes = getProperty(JavaScriptConfigParameters.INJECT_INTO_ATTRIBUTES, servletConfig);
553560
this.javascriptInjectGetForms = getProperty(JavaScriptConfigParameters.INJECT_GET_FORMS, servletConfig);

csrfguard/src/main/java/org/owasp/csrfguard/config/properties/javascript/JavaScriptConfigParameters.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ private JavaScriptConfigParameters() {}
4040
// TODO document the names of the configurations that can be used for overriding the values from the web.xml in the properties file
4141

4242
public static final StringJsConfigParameter CACHE_CONTROL = new StringJsConfigParameter("cache-control", "org.owasp.csrfguard.JavascriptServlet.cacheControl", "private, max-age=28800");
43+
public static final StringJsConfigParameter CACHE_CONTROL_TAGGED = new StringJsConfigParameter("cache-control", "org.owasp.csrfguard.JavascriptServlet.cacheControlTagged", "private, max-age=600");
4344
public static final StringJsConfigParameter REFERER_PATTERN = new StringJsConfigParameter("referer-pattern", "org.owasp.csrfguard.JavascriptServlet.refererPattern", DEFAULT_REFERER_PATTERN);
4445
public static final StringJsConfigParameter UNPROTECTED_EXTENSIONS = new StringJsConfigParameter("unprotected-extensions", "org.owasp.csrfguard.JavascriptServlet.UnprotectedExtensions", StringUtils.EMPTY);
4546
public static final StringJsConfigParameter SOURCE_FILE_LOCATION = new StringJsConfigParameter("source-file", "org.owasp.csrfguard.JavascriptServlet.sourceFile", null);

csrfguard/src/main/java/org/owasp/csrfguard/servlet/JavaScriptServlet.java

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.owasp.csrfguard.session.LogicalSession;
3939
import org.owasp.csrfguard.token.storage.LogicalSessionExtractor;
4040
import org.owasp.csrfguard.token.transferobject.TokenTO;
41+
import org.owasp.csrfguard.util.ConvertUtil;
4142
import org.owasp.csrfguard.util.CsrfGuardUtils;
4243
import org.slf4j.Logger;
4344
import org.slf4j.LoggerFactory;
@@ -50,6 +51,7 @@
5051
import java.net.MalformedURLException;
5152
import java.net.URL;
5253
import java.nio.charset.Charset;
54+
import java.security.MessageDigest;
5355
import java.util.*;
5456
import java.util.function.BiFunction;
5557
import java.util.regex.Pattern;
@@ -198,6 +200,12 @@ public void doPost(final HttpServletRequest request, final HttpServletResponse r
198200
}
199201
}
200202

203+
public static String getJavaScriptEtag(final HttpServletRequest request) {
204+
final CsrfGuard csrfGuard = CsrfGuard.getInstance();
205+
final String[] replacementList = JS_REPLACEMENT_MAP.values().stream().map(v -> v.apply(csrfGuard, request)).toArray(String[]::new);
206+
return md5Hex(StringUtils.replaceEach(csrfGuard.getJavascriptTemplateCode(), JS_REPLACEMENT_MAP.keySet().toArray(new String[0]), replacementList));
207+
}
208+
201209
private static void writeTokens(final HttpServletResponse response, final TokenTO tokenTO) throws IOException {
202210
final String jsonTokenTO = tokenTO.toString();
203211

@@ -210,22 +218,24 @@ private static void writeTokens(final HttpServletResponse response, final TokenT
210218

211219
private static void writeJavaScript(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
212220
final CsrfGuard csrfGuard = CsrfGuard.getInstance();
221+
final String[] replacementList = JS_REPLACEMENT_MAP.values().stream().map(v -> v.apply(csrfGuard, request)).toArray(String[]::new);
222+
final String code = StringUtils.replaceEach(csrfGuard.getJavascriptTemplateCode(), JS_REPLACEMENT_MAP.keySet().toArray(new String[0]), replacementList);
223+
final String etag = md5Hex(code);
224+
if (etag != null) response.setHeader("ETag", etag);
225+
response.setContentType(JAVASCRIPT_MIME_TYPE);
213226

214-
/* cannot cache if rotate or token-per-page is enabled */
215-
if (csrfGuard.isRotateEnabled() || csrfGuard.isTokenPerPageEnabled()) {
216-
response.setHeader("Cache-Control", "no-cache, no-store");
217-
response.setHeader("Pragma", "no-cache");
218-
response.setHeader("Expires", "0");
227+
if (request.getHeader("If-None-Match") != null && request.getHeader("If-None-Match").equals(etag)) {
228+
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
229+
return;
230+
}
231+
232+
if (request.getParameter("tag") != null && request.getParameter("tag").equals(etag)) {
233+
response.setHeader("Cache-Control", csrfGuard.getJavascriptTaggedCacheControl());
234+
} else if (csrfGuard.isRotateEnabled() || csrfGuard.isTokenPerPageEnabled()) {
235+
response.setHeader("Cache-Control", "private, no-cache, no-store, max-age=0");
219236
} else {
220237
response.setHeader("Cache-Control", csrfGuard.getJavascriptCacheControl());
221238
}
222-
223-
response.setContentType(JAVASCRIPT_MIME_TYPE);
224-
225-
final String[] replacementList = JS_REPLACEMENT_MAP.values().stream().map(v -> v.apply(csrfGuard, request)).toArray(String[]::new);
226-
227-
final String code = StringUtils.replaceEach(csrfGuard.getJavascriptTemplateCode(), JS_REPLACEMENT_MAP.keySet().toArray(new String[0]), replacementList);
228-
229239
response.getWriter().write(code);
230240
}
231241

@@ -311,4 +321,14 @@ private void writeJavaScript(final CsrfGuard csrfGuard, final HttpServletRequest
311321

312322
writeJavaScript(request, response);
313323
}
324+
325+
private static String md5Hex(String input) {
326+
try {
327+
byte[] digest = MessageDigest.getInstance("MD5").digest(input.getBytes());
328+
return ConvertUtil.bytesToHex(digest);
329+
} catch (Exception e) {
330+
LOGGER.debug("Unable to generate ETag", e);
331+
}
332+
return null;
333+
}
314334
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* The OWASP CSRFGuard Project, BSD License
3+
* Copyright (c) 2011, Eric Sheridan ([email protected])
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are met:
8+
*
9+
* 1. Redistributions of source code must retain the above copyright notice,
10+
* this list of conditions and the following disclaimer.
11+
* 2. Redistributions in binary form must reproduce the above copyright
12+
* notice, this list of conditions and the following disclaimer in the
13+
* documentation and/or other materials provided with the distribution.
14+
* 3. Neither the name of OWASP nor the names of its contributors may be used
15+
* to endorse or promote products derived from this software without specific
16+
* prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25+
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
*/
29+
30+
package org.owasp.csrfguard.util;
31+
32+
import java.nio.charset.StandardCharsets;
33+
34+
/**
35+
* @author Jerome Blanchard
36+
*/
37+
public class ConvertUtil {
38+
39+
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
40+
41+
public static String bytesToHex(byte[] bytes) {
42+
byte[] hexChars = new byte[bytes.length * 2];
43+
for (int j = 0; j < bytes.length; j++) {
44+
int v = bytes[j] & 0xFF;
45+
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
46+
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
47+
}
48+
return new String(hexChars, StandardCharsets.UTF_8);
49+
}
50+
}

csrfguard/src/main/resources/csrfguard.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ if (owaspCSRFGuardScriptHasLoaded !== true) {
683683

684684
/**
685685
* For the library to function correctly, all the URLs must start with a forward slash (/)
686+
* or a full URL (http://example.com, https://example.com, //example.com).
686687
* Parameters must be removed from the URL
687688
*/
688689
var normalizeUrl = function(url) {
@@ -691,10 +692,8 @@ if (owaspCSRFGuardScriptHasLoaded !== true) {
691692
return index > 0 ? currentUrl.substring(0, index) : currentUrl;
692693
}
693694

694-
/*
695-
* TODO should other checks be done here like in the isValidUrl?
696-
* Could the url parameter contain full URLs with protocol domain, port etc?
697-
*/
695+
let splittedUrl = /^(?:https?:)?\/\/[^\/]*(\/[^#?]*)?.*/.exec(url);
696+
url = (splittedUrl) ? splittedUrl[1] || "/" : url;
698697
let normalizedUrl = startsWith(url, '/') ? url : '/' + url;
699698

700699
normalizedUrl = removeParameters(normalizedUrl, '?');

0 commit comments

Comments
 (0)