Skip to content

Commit 724438a

Browse files
authored
[Security] Check auth scheme case insensitively (#31490)
According to RFC 7617, the Basic authentication scheme name should not be case sensitive. Case insensitive comparisons are also applicable for the bearer tokens where Bearer authentication scheme is used as per RFC 6750 and RFC 7235 Some Http clients may send authentication scheme names in different case types for eg. Basic, basic, BASIC, BEARER etc., so the lack of case-insensitive check is an issue when these clients try to authenticate with elasticsearch. This commit adds case-insensitive checks for Basic and Bearer authentication schemes. Closes #31486
1 parent 3b7225e commit 724438a

File tree

4 files changed

+30
-10
lines changed

4 files changed

+30
-10
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/UsernamePasswordToken.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package org.elasticsearch.xpack.core.security.authc.support;
77

8+
import org.elasticsearch.common.Strings;
89
import org.elasticsearch.common.settings.SecureString;
910
import org.elasticsearch.common.util.concurrent.ThreadContext;
1011
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
@@ -20,6 +21,8 @@ public class UsernamePasswordToken implements AuthenticationToken {
2021

2122
public static final String BASIC_AUTH_PREFIX = "Basic ";
2223
public static final String BASIC_AUTH_HEADER = "Authorization";
24+
// authorization scheme check is case-insensitive
25+
private static final boolean IGNORE_CASE_AUTH_HEADER_MATCH = true;
2326
private final String username;
2427
private final SecureString password;
2528

@@ -79,15 +82,15 @@ public int hashCode() {
7982

8083
public static UsernamePasswordToken extractToken(ThreadContext context) {
8184
String authStr = context.getHeader(BASIC_AUTH_HEADER);
82-
if (authStr == null) {
83-
return null;
84-
}
85-
8685
return extractToken(authStr);
8786
}
8887

8988
private static UsernamePasswordToken extractToken(String headerValue) {
90-
if (headerValue.startsWith(BASIC_AUTH_PREFIX) == false) {
89+
if (Strings.isNullOrEmpty(headerValue)) {
90+
return null;
91+
}
92+
if (headerValue.regionMatches(IGNORE_CASE_AUTH_HEADER_MATCH, 0, BASIC_AUTH_PREFIX, 0,
93+
BASIC_AUTH_PREFIX.length()) == false) {
9194
// the header does not start with 'Basic ' so we cannot use it, but it may be valid for another realm
9295
return null;
9396
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ private void maybeStartTokenRemover() {
10071007
*/
10081008
private String getFromHeader(ThreadContext threadContext) {
10091009
String header = threadContext.getHeader("Authorization");
1010-
if (Strings.hasLength(header) && header.startsWith("Bearer ")
1010+
if (Strings.hasText(header) && header.regionMatches(true, 0, "Bearer ", 0, "Bearer ".length())
10111011
&& header.length() > "Bearer ".length()) {
10121012
return header.substring("Bearer ".length());
10131013
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes;
7272
import static org.hamcrest.Matchers.containsString;
7373
import static org.hamcrest.Matchers.notNullValue;
74+
import static org.hamcrest.Matchers.nullValue;
7475
import static org.mockito.Matchers.any;
7576
import static org.mockito.Matchers.anyString;
7677
import static org.mockito.Matchers.eq;
@@ -162,7 +163,7 @@ public void testAttachAndGetToken() throws Exception {
162163
mockGetTokenFromId(token);
163164

164165
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
165-
requestContext.putHeader("Authorization", "Bearer " + tokenService.getUserTokenString(token));
166+
requestContext.putHeader("Authorization", randomFrom("Bearer ", "BEARER ", "bearer ") + tokenService.getUserTokenString(token));
166167

167168
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
168169
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@@ -183,6 +184,21 @@ public void testAttachAndGetToken() throws Exception {
183184
}
184185
}
185186

187+
public void testInvalidAuthorizationHeader() throws Exception {
188+
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
189+
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
190+
String token = randomFrom("", " ");
191+
String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic ");
192+
requestContext.putHeader("Authorization", authScheme + token);
193+
194+
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
195+
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
196+
tokenService.getAndValidateToken(requestContext, future);
197+
UserToken serialized = future.get();
198+
assertThat(serialized, nullValue());
199+
}
200+
}
201+
186202
public void testRotateKey() throws Exception {
187203
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
188204
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/UsernamePasswordTokenTests.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public void testPutToken() throws Exception {
4545

4646
public void testExtractToken() throws Exception {
4747
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
48-
String header = "Basic " + Base64.getEncoder().encodeToString("user1:test123".getBytes(StandardCharsets.UTF_8));
48+
final String header = randomFrom("Basic ", "basic ", "BASIC ")
49+
+ Base64.getEncoder().encodeToString("user1:test123".getBytes(StandardCharsets.UTF_8));
4950
threadContext.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, header);
5051
UsernamePasswordToken token = UsernamePasswordToken.extractToken(threadContext);
5152
assertThat(token, notNullValue());
@@ -54,7 +55,7 @@ public void testExtractToken() throws Exception {
5455
}
5556

5657
public void testExtractTokenInvalid() throws Exception {
57-
String[] invalidValues = { "Basic ", "Basic f" };
58+
final String[] invalidValues = { "Basic ", "Basic f", "basic " };
5859
for (String value : invalidValues) {
5960
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
6061
threadContext.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, value);
@@ -70,7 +71,7 @@ public void testExtractTokenInvalid() throws Exception {
7071

7172
public void testHeaderNotMatchingReturnsNull() {
7273
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
73-
String header = randomFrom("BasicBroken", "invalid", "Basic");
74+
final String header = randomFrom("Basic", "BasicBroken", "invalid", " basic ");
7475
threadContext.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, header);
7576
UsernamePasswordToken extracted = UsernamePasswordToken.extractToken(threadContext);
7677
assertThat(extracted, nullValue());

0 commit comments

Comments
 (0)