From e97408a5ad2b36e1b70e9630337d96556ef1a33b Mon Sep 17 00:00:00 2001 From: JinwooHwang Date: Fri, 31 Jul 2026 17:48:03 -0400 Subject: [PATCH 1/3] Secure JMX query expression deserialization via ValidatingObjectInputStream --- .../web/shell/HttpOperationInvoker.java | 9 ++++++- .../controllers/ShellCommandsController.java | 21 ++++++++++++--- .../QueryExpDeserializationTest.java | 27 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java index bc70f5f4be6..b4f63c60ecf 100644 --- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java +++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java @@ -17,6 +17,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.util.Base64; import java.util.Properties; import java.util.Set; import java.util.concurrent.Executors; @@ -383,8 +384,14 @@ public Object invoke(final String resourceName, final String operationName, fina public Set queryNames(final ObjectName objectName, final QueryExp queryExpression) { final URI link = HttpRequester.createURI(baseUrl, "/mbean/query"); - Object content = new QueryParameterSource(objectName, queryExpression); try { + final MultiValueMap content = new LinkedMultiValueMap(); + content.add("objectName", objectName.toString()); + if (queryExpression != null) { + content.add("queryExpression", + Base64.getEncoder().encodeToString(IOUtils.serializeObject(queryExpression))); + } + return (Set) IOUtils .deserializeObject(httpRequester.post(link, content, byte[].class)); } catch (Exception e) { diff --git a/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java index 3bc43a48e8d..27c794bb3dc 100644 --- a/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java +++ b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java @@ -17,9 +17,11 @@ import static org.apache.commons.io.IOUtils.toInputStream; import static org.apache.geode.management.internal.web.util.UriUtils.decode; +import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Path; +import java.util.Base64; import java.util.Optional; import java.util.Set; @@ -29,9 +31,11 @@ import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; +import javax.management.QueryExp; import javax.management.ReflectionException; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.serialization.ValidatingObjectInputStream; import org.apache.commons.lang3.ArrayUtils; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; @@ -162,11 +166,20 @@ public ResponseEntity invoke(@RequestParam("resourceName") final String resou } @RequestMapping(method = RequestMethod.POST, value = "/mbean/query") - public ResponseEntity queryNames(@RequestBody final QueryParameterSource query) - throws IOException { + public ResponseEntity queryNames(@RequestParam("objectName") final String objectName, + @RequestParam(value = "queryExpression", required = false) final String queryExpressionBase64) + throws Exception { // Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException - final Set objectNames = - getMBeanServer().queryNames(query.getObjectName(), query.getQueryExpression()); + ObjectName name = ObjectName.getInstance(decode(objectName)); + QueryExp query = null; + if (queryExpressionBase64 != null) { + byte[] decodedBytes = Base64.getDecoder().decode(queryExpressionBase64); + try (ValidatingObjectInputStream ois = new ValidatingObjectInputStream(new ByteArrayInputStream(decodedBytes))) { + ois.accept("javax.management.*", "java.lang.*", "java.util.*"); + query = (QueryExp) ois.readObject(); + } + } + final Set objectNames = getMBeanServer().queryNames(name, query); return new ResponseEntity<>(IOUtils.serializeObject(objectNames), HttpStatus.OK); } diff --git a/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java b/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java new file mode 100644 index 00000000000..b8d2eb909ee --- /dev/null +++ b/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java @@ -0,0 +1,27 @@ +package org.apache.geode.management.internal.web.controllers; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import javax.management.Query; +import javax.management.QueryExp; +import org.apache.commons.io.serialization.ValidatingObjectInputStream; +import org.junit.Test; + +public class QueryExpDeserializationTest { + @Test + public void testQueryExp() throws Exception { + QueryExp query = Query.eq(Query.attr("Name"), Query.value("mock")); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(query); + oos.close(); + + byte[] decoded = baos.toByteArray(); + ValidatingObjectInputStream ois = new ValidatingObjectInputStream(new ByteArrayInputStream(decoded)); + ois.accept("javax.management.*", "java.lang.*", "java.util.*"); + + QueryExp q = (QueryExp) ois.readObject(); + System.out.println(q); + } +} From e7c68f1238da8a3183fa823ba430f5570693a2b0 Mon Sep 17 00:00:00 2001 From: JinwooHwang Date: Fri, 31 Jul 2026 18:10:11 -0400 Subject: [PATCH 2/3] fix formatting --- .../web/controllers/ShellCommandsController.java | 3 ++- .../web/controllers/QueryExpDeserializationTest.java | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java index 06c235148b9..5e194b19081 100644 --- a/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java +++ b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java @@ -172,7 +172,8 @@ public ResponseEntity queryNames(@RequestParam("objectName") final String obj QueryExp query = null; if (queryExpressionBase64 != null) { byte[] decodedBytes = Base64.getDecoder().decode(queryExpressionBase64); - try (ValidatingObjectInputStream ois = new ValidatingObjectInputStream(new ByteArrayInputStream(decodedBytes))) { + try (ValidatingObjectInputStream ois = + new ValidatingObjectInputStream(new ByteArrayInputStream(decodedBytes))) { ois.accept("javax.management.*", "java.lang.*", "java.util.*"); query = (QueryExp) ois.readObject(); } diff --git a/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java b/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java index b8d2eb909ee..f9d5cd6dc06 100644 --- a/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java +++ b/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java @@ -3,8 +3,10 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; + import javax.management.Query; import javax.management.QueryExp; + import org.apache.commons.io.serialization.ValidatingObjectInputStream; import org.junit.Test; @@ -16,11 +18,12 @@ public void testQueryExp() throws Exception { ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(query); oos.close(); - + byte[] decoded = baos.toByteArray(); - ValidatingObjectInputStream ois = new ValidatingObjectInputStream(new ByteArrayInputStream(decoded)); + ValidatingObjectInputStream ois = + new ValidatingObjectInputStream(new ByteArrayInputStream(decoded)); ois.accept("javax.management.*", "java.lang.*", "java.util.*"); - + QueryExp q = (QueryExp) ois.readObject(); System.out.println(q); } From 83fbafc41f95137022ad4bb24b6a6a8f970beeef Mon Sep 17 00:00:00 2001 From: JinwooHwang Date: Fri, 31 Jul 2026 18:24:32 -0400 Subject: [PATCH 3/3] license --- .../controllers/QueryExpDeserializationTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java b/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java index f9d5cd6dc06..763e45e6bff 100644 --- a/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java +++ b/geode-web/src/test/java/org/apache/geode/management/internal/web/controllers/QueryExpDeserializationTest.java @@ -1,3 +1,17 @@ +/* + * 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.geode.management.internal.web.controllers; import java.io.ByteArrayInputStream;