From 0a38a7375621220d2f9dec9b713f9edebd6ac4df Mon Sep 17 00:00:00 2001 From: Duansg Date: Tue, 21 Jul 2026 23:19:43 +0800 Subject: [PATCH] [improve] form-urlencode SQL request body in GreptimeSqlQueryExecutor (#4223) Co-authored-by: Tomsun28 --- .../db/GreptimeSqlQueryExecutor.java | 4 ++- .../db/GreptimeSqlQueryExecutorTest.java | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutor.java b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutor.java index 0afcc8fe20..bfac687599 100644 --- a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutor.java +++ b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutor.java @@ -38,6 +38,8 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -79,7 +81,7 @@ public class GreptimeSqlQueryExecutor extends SqlQueryExecutor { headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth); } - String requestBody = "sql=" + queryString; + String requestBody = "sql=" + URLEncoder.encode(queryString, StandardCharsets.UTF_8); HttpEntity httpEntity = new HttpEntity<>(requestBody, headers); String url = greptimeProperties.httpEndpoint() + QUERY_PATH; diff --git a/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutorTest.java b/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutorTest.java index 1445ed5f0c..ea927bd403 100644 --- a/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutorTest.java +++ b/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/db/GreptimeSqlQueryExecutorTest.java @@ -20,12 +20,16 @@ package org.apache.hertzbeat.warehouse.db; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -34,6 +38,7 @@ import org.apache.hertzbeat.warehouse.store.history.tsdb.greptime.GreptimeSqlQue import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpEntity; @@ -90,6 +95,37 @@ class GreptimeSqlQueryExecutorTest { assertEquals(85.5, result.get(0).get("value")); } + @Test + @SuppressWarnings("unchecked") + void testRequestBodyIsFormEncoded() { + when(restTemplate.exchange( + any(String.class), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(GreptimeSqlQueryContent.class) + )).thenReturn(new ResponseEntity<>(new GreptimeSqlQueryContent(), HttpStatus.OK)); + + // SQL carrying form-urlencoded metacharacters that would pollute the request body + // if concatenated raw: '&' would start a second sql= parameter and override the query. + String sql = "SELECT * FROM t WHERE name = 'x&sql=DROP TABLE t'"; + greptimeSqlQueryExecutor.execute(sql); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(HttpEntity.class); + verify(restTemplate).exchange( + any(String.class), + eq(HttpMethod.POST), + captor.capture(), + eq(GreptimeSqlQueryContent.class)); + + String body = captor.getValue().getBody(); + assertNotNull(body); + // The body must be a single sql= parameter; the '&' inside the SQL must be encoded. + String encodedValue = body.substring("sql=".length()); + assertFalse(encodedValue.contains("&"), "raw '&' in body causes HTTP parameter pollution"); + // Decoding the value must yield exactly the original SQL, lossless round-trip. + assertEquals(sql, URLDecoder.decode(encodedValue, StandardCharsets.UTF_8)); + } + @Test void testExecuteError() { // Mock error response