VYPR
High severityNVD Advisory· Published Nov 26, 2025· Updated Dec 1, 2025

Apache Hive: SQL injection vulnerability when processing delete column statistics requests via the HMS Thrift APIs

CVE-2025-62728

Description

SQL injection vulnerability in Hive Metastore Server (HMS) when processing delete column statistics requests via the Thrift APIs. The vulnerability is only exploitable by trusted/authorized users/applications that are allowed to call directly the Thrift APIs. In most real-world deployments, HMS is accessible to only a handful of applications (e.g., Hiveserver2) thus the vulnerability is not exploitable. Moreover, the vulnerable code cannot be reached when metastore.try.direct.sql property is set to false.

This issue affects Apache Hive: from 4.1.0 before 4.2.0.

Users are recommended to upgrade to version 4.2.0, which fixes the issue. Users who cannot upgrade directly are encouraged to set metastore.try.direct.sql property to false if the HMS Thrift APIs are exposed to general public.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache Hive 4.1.0 to 4.2.0 contains a SQL injection in HMS delete column statistics via Thrift, exploitable only by authorized users; fixed in 4.2.0.

Vulnerability

Overview

CVE-2025-62728 is a SQL injection vulnerability in Apache Hive's Metastore's Hive Metastore Server (HMS) when processing delete column statistics requests via the Thrift APIs [1]. The root cause is the lack of input validation on the engine parameter extracted from DeleteColumnStatisticsRequest; this unvalidated value is passed directly into SQL statement construction in MetaStoreDirectSql.deleteTableColumnStatistics [3]. The vulnerable code path exists in HMS versions 4.1.0 up to but not including 4.2.0 [1].

Exploitation

Context

Exploitation requires the attacker to be a trusted or authorized user or application that can directly call the HMS Thrift APIs [1]. In typical deployments, HMS is only accessible to a limited set of applications (e.g., HiveServer2), which significantly reduces the attack surface [1]. Additionally, the vulnerable code cannot be reached when the metastore.try.direct.sql property is set to false [1]. The commit that fixes the issue includes proper escaping of column identifiers and the engine parameter to prevent SQL injection [2].

Impact

A successful SQL injection could allow an attacker to execute arbitrary SQL commands on the metastore database, potentially leading to unauthorized data access or manipulation [3]. However, due to the restricted access to the Thrift API and the availability of a configuration workaround, the practical risk is limited to be considered limited in most environments [1].

Mitigation

Users are strongly recommended to upgrade to Apache Hive 4.2.0, which contains the fix [1]. For those who cannot upgrade immediately, setting metastore.try.direct.sql to false will prevent the vulnerable code from being executed, provided that the HMS Thrift APIs are exposed to general public [1]. No other workarounds have been published.

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.hive:hive-commonMaven
>= 4.1.0, < 4.2.04.2.0
org.apache.hive:hive-metastoreMaven
>= 4.1.0, < 4.2.04.2.0

Affected products

2
  • Apache/Hivellm-fuzzy
    Range: >=4.1.0 <4.2.0
  • Apache Software Foundation/Apache Hivev5
    Range: 4.1.0

Patches

1
c18d0df27021

Enable using column identifiers with special characters when deleting table column statistics. (#6149)

https://github.com/apache/hiveKrisztian KasaNov 3, 2025via ghsa
2 files changed · +57 21
  • standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreDirectSql.java+30 14 modified
    @@ -133,6 +133,7 @@
      */
     class MetaStoreDirectSql {
       private static final int NO_BATCHING = -1, DETECT_BATCHING = 0;
    +  private static final Set<String> ALLOWED_TABLES_TO_LOCK = Set.of("NOTIFICATION_SEQUENCE");
     
       private static final Logger LOG = LoggerFactory.getLogger(MetaStoreDirectSql.class);
       private final PersistenceManager pm;
    @@ -3203,6 +3204,11 @@ private void getStatsTableListResult(
       }
     
       public void lockDbTable(String tableName) throws MetaException {
    +    // Only certain tables are allowed to be locked, and the API should restrict them.
    +    if (!ALLOWED_TABLES_TO_LOCK.contains(tableName)) {
    +      throw new MetaException("Error while locking table " + tableName);
    +    }
    +
         String lockCommand = "lock table \"" + tableName + "\" in exclusive mode";
         try {
           executeNoResult(lockCommand);
    @@ -3243,19 +3249,26 @@ public void deleteColumnStatsState(long tbl_id) throws MetaException {
       }
     
       public boolean deleteTableColumnStatistics(long tableId, List<String> colNames, String engine) {
    -    String deleteSql = "delete from " + TAB_COL_STATS + " where \"TBL_ID\" = " + tableId;
    +    String deleteSql = "delete from " + TAB_COL_STATS + " where \"TBL_ID\" = ?";
    +    List<Object> params = new ArrayList<>(colNames == null ? 2 : colNames.size() + 2);
    +    params.add(tableId);
    +
         if (colNames != null && !colNames.isEmpty()) {
    -      deleteSql += " and \"COLUMN_NAME\" in (" + colNames.stream().map(col -> "'" + col + "'").collect(Collectors.joining(",")) + ")";
    +      deleteSql += " and \"COLUMN_NAME\" in (" + makeParams(colNames.size()) + ")";
    +      params.addAll(colNames);
         }
    +
         if (engine != null) {
    -      deleteSql += " and \"ENGINE\" = '" + engine + "'";
    +      deleteSql += " and \"ENGINE\" = ?";
    +      params.add(engine);
         }
    -    try {
    -      executeNoResult(deleteSql);
    -    } catch (SQLException e) {
    -      LOG.warn("Error removing table column stats. ", e);
    +
    +    try (QueryWrapper queryParams = new QueryWrapper(pm.newQuery("javax.jdo.query.SQL", deleteSql))) {
    +      executeWithArray(queryParams.getInnerQuery(), params.toArray(), deleteSql);
    +    } catch (MetaException e) {
           return false;
         }
    +
         return true;
       }
     
    @@ -3269,17 +3282,20 @@ public List<Void> run(List<String> input) throws Exception {
                 input, Collections.emptyList(), -1);
             if (!partitionIds.isEmpty()) {
               String deleteSql = "delete from " + PART_COL_STATS + " where \"PART_ID\" in ( " + getIdListForIn(partitionIds) + ")";
    +          List<Object> params = new ArrayList<>(colNames == null ? 1 : colNames.size() + 1);
    +
               if (colNames != null && !colNames.isEmpty()) {
    -            deleteSql += " and \"COLUMN_NAME\" in (" + colNames.stream().map(col -> "'" + col + "'").collect(Collectors.joining(",")) + ")";
    +            deleteSql += " and \"COLUMN_NAME\" in (" + makeParams(colNames.size()) + ")";
    +            params.addAll(colNames);
               }
    +
               if (engine != null) {
    -            deleteSql += " and \"ENGINE\" = '" + engine + "'";
    +            deleteSql += " and \"ENGINE\" = ?";
    +            params.add(engine);
               }
    -          try {
    -            executeNoResult(deleteSql);
    -          } catch (SQLException e) {
    -            LOG.warn("Error removing partition column stats. ", e);
    -            throw new MetaException("Error removing partition column stats: " + e.getMessage());
    +
    +          try (QueryWrapper queryParams = new QueryWrapper(pm.newQuery("javax.jdo.query.SQL", deleteSql))) {
    +            executeWithArray(queryParams.getInnerQuery(), params.toArray(), deleteSql);
               }
             }
             return null;
    
  • standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestObjectStore.java+27 7 modified
    @@ -919,42 +919,48 @@ public void testTableStatisticsOps() throws Exception {
         List<ColumnStatistics> tabColStats;
         try (AutoCloseable c = deadline()) {
           tabColStats = objectStore.getTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1,
    -          Arrays.asList("test_col1", "test_col2"));
    +          Arrays.asList("test_col1", "test_col' 2"));
         }
         Assert.assertEquals(0, tabColStats.size());
     
         ColumnStatisticsDesc statsDesc = new ColumnStatisticsDesc(true, DB1, TABLE1);
         ColumnStatisticsObj statsObj1 = new ColumnStatisticsObj("test_col1", "int",
             new ColumnStatisticsData(ColumnStatisticsData._Fields.DECIMAL_STATS, new DecimalColumnStatsData(100, 1000)));
    -    ColumnStatisticsObj statsObj2 = new ColumnStatisticsObj("test_col2", "int",
    +    ColumnStatisticsObj statsObj2 = new ColumnStatisticsObj("test_col' 2", "int",
             new ColumnStatisticsData(ColumnStatisticsData._Fields.DECIMAL_STATS, new DecimalColumnStatsData(200, 2000)));
         ColumnStatistics colStats = new ColumnStatistics(statsDesc, Arrays.asList(statsObj1, statsObj2));
         colStats.setEngine(ENGINE);
         objectStore.updateTableColumnStatistics(colStats, null, 0);
     
         try (AutoCloseable c = deadline()) {
           tabColStats = objectStore.getTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1,
    -          Arrays.asList("test_col1", "test_col2"));
    +          Arrays.asList("test_col1", "test_col' 2"));
         }
         Assert.assertEquals(1, tabColStats.size());
         Assert.assertEquals(2, tabColStats.get(0).getStatsObjSize());
     
         objectStore.deleteTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1, "test_col1", ENGINE);
         try (AutoCloseable c = deadline()) {
           tabColStats = objectStore.getTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1,
    -          Arrays.asList("test_col1", "test_col2"));
    +          Arrays.asList("test_col1", "test_col' 2"));
         }
         Assert.assertEquals(1, tabColStats.size());
         Assert.assertEquals(1, tabColStats.get(0).getStatsObjSize());
     
    -    objectStore.deleteTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1, "test_col2", ENGINE);
    +    objectStore.deleteTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1, "test_col' 2", ENGINE);
         try (AutoCloseable c = deadline()) {
           tabColStats = objectStore.getTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1,
    -          Arrays.asList("test_col1", "test_col2"));
    +          Arrays.asList("test_col1", "test_col' 2"));
         }
         Assert.assertEquals(0, tabColStats.size());
       }
     
    +  @Test
    +  public void testDeleteTableColumnStatisticsWhenEngineHasSpecialCharacter() throws Exception {
    +    createPartitionedTable(true, true);
    +    objectStore.deleteTableColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1, "test_col1", "special '");
    +  }
    +
       @Test
       public void testPartitionStatisticsOps() throws Exception {
         createPartitionedTable(true, true);
    @@ -1006,6 +1012,14 @@ public void testPartitionStatisticsOps() throws Exception {
         Assert.assertEquals(0, stat.size());
       }
     
    +  @Test
    +  public void testDeletePartitionColumnStatisticsWhenEngineHasSpecialCharacter() throws Exception {
    +    createPartitionedTable(true, true);
    +    objectStore.deletePartitionColumnStatistics(DEFAULT_CATALOG_NAME, DB1, TABLE1,
    +            "test_part_col=a2", List.of("a2"), null, "special '");
    +  }
    +
    +
       @Test
       public void testAggrStatsUseDB() throws Exception {
         Configuration conf2 = MetastoreConf.newMetastoreConf(conf);
    @@ -1051,7 +1065,7 @@ private void createPartitionedTable(boolean withPrivileges, boolean withStatisti
                 .setDbName(DB1)
                 .setTableName(TABLE1)
                 .addCol("test_col1", "int")
    -            .addCol("test_col2", "int")
    +            .addCol("test_col' 2", "int")
                 .addPartCol("test_part_col", "int")
                 .addCol("test_bucket_col", "int", "test bucket col comment")
                 .addCol("test_skewed_col", "int", "test skewed col comment")
    @@ -1239,6 +1253,12 @@ protected Database getJdoResult(ObjectStore.GetHelper<Database> ctx) throws Meta
         Assert.assertEquals(1, directSqlErrors.getCount());
       }
     
    +  @Test(expected = MetaException.class)
    +  public void testLockDbTableThrowsExceptionWhenTableIsNotAllowedToLock() throws Exception {
    +    MetaStoreDirectSql metaStoreDirectSql = new MetaStoreDirectSql(objectStore.getPersistenceManager(), conf, null);
    +    metaStoreDirectSql.lockDbTable("TBLS");
    +  }
    +
       @Deprecated
       private static void dropAllStoreObjects(RawStore store)
           throws MetaException, InvalidObjectException, InvalidInputException {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.