Add null check in CsrfFilter and CsrfWebFilter

Solve the problem that CsrfFilter and CsrfWebFilter
throws NPE exception when comparing two byte array
is equal in low JDK version.

When JDK version is lower than 1.8.0_45, method
java.security.MessageDigest#isEqual does not verify
whether the two arrays are null. And the above two
class call this method without null judgment.

ZiQiang Zhao<1694392889@qq.com>
This commit is contained in:
佚名 2021-04-06 19:19:39 +08:00 committed by Josh Cummings
parent df8abcfae7
commit 87ed527023
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
4 changed files with 49 additions and 20 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -174,17 +174,18 @@ public final class CsrfFilter extends OncePerRequestFilter {
* @return
*/
private static boolean equalsConstantTime(String expected, String actual) {
byte[] expectedBytes = bytesUtf8(expected);
byte[] actualBytes = bytesUtf8(actual);
if (expected == actual) {
return true;
}
if (expected == null || actual == null) {
return false;
}
// Encode after ensure that the string is not null
byte[] expectedBytes = Utf8.encode(expected);
byte[] actualBytes = Utf8.encode(actual);
return MessageDigest.isEqual(expectedBytes, actualBytes);
}
private static byte[] bytesUtf8(String s) {
// need to check if Utf8.encode() runs in constant time (probably not).
// This may leak length of string.
return (s != null) ? Utf8.encode(s) : null;
}
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -177,17 +177,18 @@ public class CsrfWebFilter implements WebFilter {
* @return
*/
private static boolean equalsConstantTime(String expected, String actual) {
byte[] expectedBytes = bytesUtf8(expected);
byte[] actualBytes = bytesUtf8(actual);
if (expected == actual) {
return true;
}
if (expected == null || actual == null) {
return false;
}
// Encode after ensure that the string is not null
byte[] expectedBytes = Utf8.encode(expected);
byte[] actualBytes = Utf8.encode(actual);
return MessageDigest.isEqual(expectedBytes, actualBytes);
}
private static byte[] bytesUtf8(String s) {
// need to check if Utf8.encode() runs in constant time (probably not).
// This may leak length of string.
return (s != null) ? Utf8.encode(s) : null;
}
private Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.generateToken(exchange)
.delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.security.web.csrf;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.servlet.FilterChain;
@ -96,6 +97,18 @@ public class CsrfFilterTests {
this.response = new MockHttpServletResponse();
}
@Test
public void nullConstantTimeEquals() throws Exception {
Method method = CsrfFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class);
method.setAccessible(true);
assertThat(method.invoke(CsrfFilter.class, null, null)).isEqualTo(true);
String expectedToken = "Hello—World";
String actualToken = new String("Hello—World");
assertThat(method.invoke(CsrfFilter.class, expectedToken, null)).isEqualTo(false);
assertThat(method.invoke(CsrfFilter.class, expectedToken, "hello-world")).isEqualTo(false);
assertThat(method.invoke(CsrfFilter.class, expectedToken, actualToken)).isEqualTo(true);
}
@Test
public void constructorNullRepository() {
assertThatIllegalArgumentException().isThrownBy(() -> new CsrfFilter(null));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,8 @@
package org.springframework.security.web.server.csrf;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@ -65,6 +67,18 @@ public class CsrfWebFilterTests {
private MockServerWebExchange post = MockServerWebExchange.from(MockServerHttpRequest.post("/"));
@Test
public void nullConstantTimeEquals() throws Exception {
Method method = CsrfWebFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class);
method.setAccessible(true);
assertThat(method.invoke(CsrfWebFilter.class, null, null)).isEqualTo(true);
String expectedToken = "Hello—World";
String actualToken = new String("Hello—World");
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, null)).isEqualTo(false);
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, "hello-world")).isEqualTo(false);
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, actualToken)).isEqualTo(true);
}
@Test
public void filterWhenGetThenSessionNotCreatedAndChainContinues() {
PublisherProbe<Void> chainResult = PublisherProbe.empty();