This is an automated email from the ASF dual-hosted git repository.
soumyakanti3578 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new 7b5964308a3 HIVE-29794: HiveServer2 auth cookie lets one user
impersonate
7b5964308a3 is described below
commit 7b5964308a3a1dff76e304a5ed18a3b9088d8418
Author: Soumyakanti Das <[email protected]>
AuthorDate: Mon Aug 10 14:31:58 2026 -0700
HIVE-29794: HiveServer2 auth cookie lets one user impersonate
---
.../apache/hive/service/auth/HttpAuthUtils.java | 27 ++++-
.../hive/service/auth/TestHttpAuthUtils.java | 134 +++++++++++++++++++++
2 files changed, 155 insertions(+), 6 deletions(-)
diff --git a/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
b/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
index efaec8a6922..00f291375f0 100644
--- a/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
+++ b/service/src/java/org/apache/hive/service/auth/HttpAuthUtils.java
@@ -18,6 +18,9 @@
package org.apache.hive.service.auth;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.util.Arrays;
@@ -83,8 +86,10 @@ public static String getKerberosServiceTicket(String
principal, String host, Str
*/
public static String createCookieToken(String clientUserName) {
StringBuilder sb = new StringBuilder();
-
sb.append(COOKIE_CLIENT_USER_NAME).append(COOKIE_KEY_VALUE_SEPARATOR).append(clientUserName).
- append(COOKIE_ATTR_SEPARATOR);
+ sb.append(COOKIE_CLIENT_USER_NAME)
+ .append(COOKIE_KEY_VALUE_SEPARATOR)
+ .append(URLEncoder.encode(clientUserName, StandardCharsets.UTF_8))
+ .append(COOKIE_ATTR_SEPARATOR);
sb.append(COOKIE_CLIENT_RAND_NUMBER).append(COOKIE_KEY_VALUE_SEPARATOR).
append((new SecureRandom()).nextLong());
return sb.toString();
@@ -98,11 +103,17 @@ public static String createCookieToken(String
clientUserName) {
public static String getUserNameFromCookieToken(String tokenStr) {
Map<String, String> map = splitCookieToken(tokenStr);
- if (!map.keySet().equals(COOKIE_ATTRIBUTES)) {
- LOG.error("Invalid token with missing attributes " + tokenStr);
+ if (map == null || !map.keySet().equals(COOKIE_ATTRIBUTES)) {
+ LOG.error("Invalid token with missing attributes {}", tokenStr);
+ return null;
+ }
+ try {
+ // Reverse the encoding applied in createCookieToken.
+ return URLDecoder.decode(map.get(COOKIE_CLIENT_USER_NAME),
StandardCharsets.UTF_8);
+ } catch (IllegalArgumentException e) {
+ LOG.error("Invalid token with malformed user name encoding {}",
tokenStr, e);
return null;
}
- return map.get(COOKIE_CLIENT_USER_NAME);
}
/**
@@ -124,7 +135,11 @@ private static Map<String, String> splitCookieToken(String
tokenStr) {
}
String key = part.substring(0, separator);
String value = part.substring(separator + 1);
- map.put(key, value);
+ // Reject duplicate keys to keep the token unambiguous.
+ if (map.put(key, value) != null) {
+ LOG.error("Invalid token with duplicate attribute {}: {}", key,
tokenStr);
+ return null;
+ }
}
return map;
}
diff --git
a/service/src/test/org/apache/hive/service/auth/TestHttpAuthUtils.java
b/service/src/test/org/apache/hive/service/auth/TestHttpAuthUtils.java
new file mode 100644
index 00000000000..b4ec8126dd9
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/TestHttpAuthUtils.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.service.auth;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests for {@link HttpAuthUtils}.
+ */
+public class TestHttpAuthUtils {
+
+ /**
+ * A token created for a username must parse back to the same username.
+ */
+ private void assertRoundTrips(String userName) {
+ String token = HttpAuthUtils.createCookieToken(userName);
+ assertEquals(userName, HttpAuthUtils.getUserNameFromCookieToken(token));
+ }
+
+ /**
+ * A malformed or ambiguous token must be rejected by the parser.
+ */
+ private void assertRejected(String tokenStr) {
+ assertNull(HttpAuthUtils.getUserNameFromCookieToken(tokenStr));
+ }
+
+ @Test
+ public void testSimpleUserNameRoundTrips() {
+ assertRoundTrips("alice");
+ }
+
+ @Test
+ public void testUserNameWithSpaceRoundTrips() {
+ assertRoundTrips("alice smith");
+ }
+
+ @Test
+ public void testUserNameWithPlusRoundTrips() {
+ assertRoundTrips("alice+smith");
+ }
+
+ @Test
+ public void testUserNameWithPercentRoundTrips() {
+ assertRoundTrips("100%user");
+ }
+
+ /**
+ * The key/value separator must be encoded in the username so it cannot
+ * forge a second field; the token must parse back to that user.
+ */
+ @Test
+ public void testUserNameWithEqualsRoundTrips() {
+ assertRoundTrips("a=b");
+ }
+
+ /**
+ * A username containing the cookie delimiters must not
+ * inject a second {@code cu} field; the token must parse back to that user.
+ */
+ @Test
+ public void testUserNameWithCookieDelimitersDoesNotInjectSecondUser() {
+ assertRoundTrips("alice&cu=admin");
+ }
+
+ @Test
+ public void testUserNameWithTrailingRandFieldDoesNotOverride() {
+ assertRoundTrips("alice&cu=admin&rn=0");
+ }
+
+ /**
+ * A token with a duplicate {@code cu} field is ambiguous and must be
rejected,
+ * so an injected second username cannot override the authenticated one.
+ */
+ @Test
+ public void testMaliciousDuplicateCuIsRejected() {
+ assertRejected("cu=alice&cu=admin&rn=123");
+ }
+
+ /**
+ * A token whose username contains malformed percent-encoding must be
rejected.
+ */
+ @Test
+ public void testMalformedPercentEncodingIsRejected() {
+ assertRejected("cu=alice%ZZ&rn=123");
+ }
+
+ /**
+ * A token missing a required attribute must be rejected.
+ */
+ @Test
+ public void testMissingAttributesRejected() {
+ assertRejected("cu=alice"); // no rn
+ assertRejected("rn=123"); // no cu
+ }
+
+ /**
+ * A token part without a key/value separator is malformed and must be
rejected.
+ */
+ @Test
+ public void testTokenWithoutSeparatorRejected() {
+ assertRejected("aliceadmin");
+ }
+
+ /**
+ * A token carrying an unknown attribute beyond {@code cu} and {@code rn}
+ * must be rejected.
+ */
+ @Test
+ public void testUnknownAttributeRejected() {
+ assertRejected("cu=alice&rn=123&x=1");
+ }
+}
+
+
+