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");
+  }
+}
+
+
+

Reply via email to