Skip to content

Commit 942d537

Browse files
bizybotYogesh Gaikwad
authored andcommitted
[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 4a00522 commit 942d537

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

Lines changed: 8 additions & 5 deletions
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,7 @@ private void maybeStartTokenRemover() {
10211021
*/
10221022
String getFromHeader(ThreadContext threadContext) {
10231023
String header = threadContext.getHeader("Authorization");
1024-
if (Strings.hasLength(header) && header.startsWith("Bearer ")
1024+
if (Strings.hasText(header) && header.regionMatches(true, 0, "Bearer ", 0, "Bearer ".length())
10251025
&& header.length() > "Bearer ".length()) {
10261026
return header.substring("Bearer ".length());
10271027
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes;
8282
import static org.hamcrest.Matchers.containsString;
8383
import static org.hamcrest.Matchers.notNullValue;
84+
import static org.hamcrest.Matchers.nullValue;
8485
import static org.mockito.Matchers.any;
8586
import static org.mockito.Matchers.anyString;
8687
import static org.mockito.Matchers.eq;
@@ -192,7 +193,7 @@ public void testAttachAndGetToken() throws Exception {
192193
mockGetTokenFromId(token);
193194

194195
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
195-
requestContext.putHeader("Authorization", "Bearer " + tokenService.getUserTokenString(token));
196+
requestContext.putHeader("Authorization", randomFrom("Bearer ", "BEARER ", "bearer ") + tokenService.getUserTokenString(token));
196197

197198
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
198199
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@@ -213,6 +214,21 @@ public void testAttachAndGetToken() throws Exception {
213214
}
214215
}
215216

217+
public void testInvalidAuthorizationHeader() throws Exception {
218+
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
219+
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
220+
String token = randomFrom("", " ");
221+
String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic ");
222+
requestContext.putHeader("Authorization", authScheme + token);
223+
224+
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
225+
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
226+
tokenService.getAndValidateToken(requestContext, future);
227+
UserToken serialized = future.get();
228+
assertThat(serialized, nullValue());
229+
}
230+
}
231+
216232
public void testRotateKey() throws Exception {
217233
assumeFalse("internally managed keys do not work in a mixed cluster", mixedCluster);
218234
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);

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

Lines changed: 4 additions & 3 deletions
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)