CVE-2026-27172
Description
The ConsulRegistry in the camel-consul component (class org.apache.camel.component.consul.ConsulRegistry and its inner ConsulRegistryUtils.deserialize method) read Java-serialized values from the Consul KV store and passed them to ObjectInputStream.readObject() without configuring an ObjectInputFilter. An attacker who can write to the Consul KV store backing a Camel ConsulRegistry instance could inject a malicious serialized Java object that is deserialized the next time Camel performs a lookup against that registry, leading to arbitrary code execution in the Camel process. The issue mirrors the class of vulnerability already addressed for other Camel components in CVE-2024-22369, CVE-2024-23114 and CVE-2026-25747, and was overlooked during the original remediation of those CVEs.
This issue affects Apache Camel: from 3.0.0 before 4.14.6, from 4.15.0 before 4.18.1.
Users are recommended to upgrade to version 4.19.0, which fixes the issue. If users are on the 4.14.x LTS releases stream, then they are suggested to upgrade to 4.14.6. If users are on the 4.18.x releases stream, then they are suggested to upgrade to 4.18.1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.camel:camel-consulMaven | >= 3.0.0, < 4.14.6 | 4.14.6 |
org.apache.camel:camel-consulMaven | >= 4.15.0, < 4.18.1 | 4.18.1 |
Affected products
1Patches
34b540e6e20baCAMEL-23029 - Camel-Consul: Add ObjectInputFilter String pattern parameter in ConsulRegistry to be used in deserialize operations (#21532)
2 files changed · +45 −11
components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulRegistry.java+42 −9 modified@@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -52,6 +53,7 @@ public class ConsulRegistry implements Registry { private int port = 8500; private Consul consul; private KeyValueClient kvClient; + private String deserializationFilter = "java.**;org.apache.camel.**;!*"; /* constructor with default port */ public ConsulRegistry(String hostname) { @@ -70,6 +72,9 @@ private ConsulRegistry(Builder builder) { this.hostname = builder.hostname; this.port = builder.port; this.consul = Consul.builder().withUrl("http://" + this.hostname + ":" + this.port).build(); + if (builder.deserializationFilter != null) { + this.deserializationFilter = builder.deserializationFilter; + } } @Override @@ -80,7 +85,7 @@ public Object lookupByName(String key) { return kvClient.getValueAsString(key).map(result -> { byte[] postDecodedValue = ConsulRegistryUtils.decodeBase64(result); - return ConsulRegistryUtils.deserialize(postDecodedValue); + return ConsulRegistryUtils.deserialize(postDecodedValue, deserializationFilter); }).orElse(null); } @@ -219,7 +224,7 @@ public void put(String key, Object object) { if (lookupByName(key) != null) { remove(key); } - Object clone = ConsulRegistryUtils.clone((Serializable) object); + Object clone = ConsulRegistryUtils.clone((Serializable) object, deserializationFilter); byte[] serializedObject = ConsulRegistryUtils.serialize((Serializable) clone); // pre-encode due native encoding issues String value = ConsulRegistryUtils.encodeBase64(serializedObject); @@ -235,6 +240,7 @@ public static class Builder { String hostname; // optional parameter Integer port = 8500; + String deserializationFilter; public Builder(String hostname) { this.hostname = hostname; @@ -245,6 +251,11 @@ public Builder port(Integer port) { return this; } + public Builder deserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + return this; + } + public ConsulRegistry build() { return new ConsulRegistry(this); } @@ -266,6 +277,23 @@ public void setPort(int port) { this.port = port; } + /** + * Gets the deserialization filter applied when reading objects from Consul KV store. + */ + public String getDeserializationFilter() { + return deserializationFilter; + } + + /** + * Sets a deserialization filter while reading objects from Consul KV store. By default the filter will allow all + * java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be + * blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be + * deserialized. + */ + public void setDeserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + } + static final class ConsulRegistryUtils { private ConsulRegistryUtils() { @@ -296,11 +324,15 @@ static String encodeBase64(final byte[] binaryData) { /** * Deserializes an object out of the given byte array. * - * @param bytes the byte array to deserialize from - * @return an {@link Object} deserialized from the given byte array + * @param bytes the byte array to deserialize from + * @param deserializationFilter the deserialization filter to apply (e.g. "java.**;org.apache.camel.**;!*") + * @return an {@link Object} deserialized from the given byte array */ - static Object deserialize(byte[] bytes) { + static Object deserialize(byte[] bytes, String deserializationFilter) { try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) { + if (deserializationFilter != null && !deserializationFilter.isEmpty()) { + in.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter)); + } return in.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException(e); @@ -310,11 +342,12 @@ static Object deserialize(byte[] bytes) { /** * A deep serialization based clone * - * @param object the object to clone - * @return a deep clone + * @param object the object to clone + * @param deserializationFilter the deserialization filter to apply + * @return a deep clone */ - static Object clone(Serializable object) { - return deserialize(serialize(object)); + static Object clone(Serializable object, String deserializationFilter) { + return deserialize(serialize(object), deserializationFilter); } /**
components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryUtilsTest.java+3 −2 modified@@ -33,9 +33,10 @@ public class ConsulRegistryUtilsTest { @Test public void encodeDecode() { + final String filter = "java.**;org.apache.camel.**;!*"; final List<String> src = Arrays.asList("one", "\u0434\u0432\u0430", "t\u0159i"); final byte[] serialized = ConsulRegistryUtils.serialize((Serializable) src); - assertEquals(src, ConsulRegistryUtils.deserialize(serialized)); + assertEquals(src, ConsulRegistryUtils.deserialize(serialized, filter)); final String encoded = ConsulRegistryUtils.encodeBase64(serialized); assertEquals("rO0ABXNyABpqYXZhLnV0aWwuQXJyYXlzJEFycmF5TGlzdNmkPL7NiAbSAgABWwABYXQAE1tMamF2YS9sYW5nL09iamVjdDt4" + "cHVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAN0AANvbmV0AAbQtNCy0LB0AAR0xZlp", @@ -53,7 +54,7 @@ public void encodeDecode() { -48, -76, -48, -78, -48, -80, 116, 0, 4, 116, -59, -103, 105 }, decoded); - assertEquals(src, ConsulRegistryUtils.deserialize(decoded)); + assertEquals(src, ConsulRegistryUtils.deserialize(decoded, filter)); } }
55dd9f8ce5f6CAMEL-23029 - Camel-Consul: Add ObjectInputFilter String pattern parameter in ConsulRegistry to be used in deserialize operations (#21530)
2 files changed · +45 −11
components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulRegistry.java+42 −9 modified@@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -52,6 +53,7 @@ public class ConsulRegistry implements Registry { private int port = 8500; private Consul consul; private KeyValueClient kvClient; + private String deserializationFilter = "java.**;org.apache.camel.**;!*"; /* constructor with default port */ public ConsulRegistry(String hostname) { @@ -70,6 +72,9 @@ private ConsulRegistry(Builder builder) { this.hostname = builder.hostname; this.port = builder.port; this.consul = Consul.builder().withUrl("http://" + this.hostname + ":" + this.port).build(); + if (builder.deserializationFilter != null) { + this.deserializationFilter = builder.deserializationFilter; + } } @Override @@ -80,7 +85,7 @@ public Object lookupByName(String key) { return kvClient.getValueAsString(key).map(result -> { byte[] postDecodedValue = ConsulRegistryUtils.decodeBase64(result); - return ConsulRegistryUtils.deserialize(postDecodedValue); + return ConsulRegistryUtils.deserialize(postDecodedValue, deserializationFilter); }).orElse(null); } @@ -219,7 +224,7 @@ public void put(String key, Object object) { if (lookupByName(key) != null) { remove(key); } - Object clone = ConsulRegistryUtils.clone((Serializable) object); + Object clone = ConsulRegistryUtils.clone((Serializable) object, deserializationFilter); byte[] serializedObject = ConsulRegistryUtils.serialize((Serializable) clone); // pre-encode due native encoding issues String value = ConsulRegistryUtils.encodeBase64(serializedObject); @@ -239,6 +244,7 @@ public static class Builder { String hostname; // optional parameter Integer port = 8500; + String deserializationFilter; public Builder(String hostname) { this.hostname = hostname; @@ -249,6 +255,11 @@ public Builder port(Integer port) { return this; } + public Builder deserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + return this; + } + public ConsulRegistry build() { return new ConsulRegistry(this); } @@ -270,6 +281,23 @@ public void setPort(int port) { this.port = port; } + /** + * Gets the deserialization filter applied when reading objects from Consul KV store. + */ + public String getDeserializationFilter() { + return deserializationFilter; + } + + /** + * Sets a deserialization filter while reading objects from Consul KV store. By default the filter will allow all + * java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be + * blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be + * deserialized. + */ + public void setDeserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + } + static final class ConsulRegistryUtils { private ConsulRegistryUtils() { @@ -300,11 +328,15 @@ static String encodeBase64(final byte[] binaryData) { /** * Deserializes an object out of the given byte array. * - * @param bytes the byte array to deserialize from - * @return an {@link Object} deserialized from the given byte array + * @param bytes the byte array to deserialize from + * @param deserializationFilter the deserialization filter to apply (e.g. "java.**;org.apache.camel.**;!*") + * @return an {@link Object} deserialized from the given byte array */ - static Object deserialize(byte[] bytes) { + static Object deserialize(byte[] bytes, String deserializationFilter) { try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) { + if (deserializationFilter != null && !deserializationFilter.isEmpty()) { + in.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter)); + } return in.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException(e); @@ -314,11 +346,12 @@ static Object deserialize(byte[] bytes) { /** * A deep serialization based clone * - * @param object the object to clone - * @return a deep clone + * @param object the object to clone + * @param deserializationFilter the deserialization filter to apply + * @return a deep clone */ - static Object clone(Serializable object) { - return deserialize(serialize(object)); + static Object clone(Serializable object, String deserializationFilter) { + return deserialize(serialize(object), deserializationFilter); } /**
components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryUtilsTest.java+3 −2 modified@@ -33,9 +33,10 @@ public class ConsulRegistryUtilsTest { @Test public void encodeDecode() { + final String filter = "java.**;org.apache.camel.**;!*"; final List<String> src = Arrays.asList("one", "\u0434\u0432\u0430", "t\u0159i"); final byte[] serialized = ConsulRegistryUtils.serialize((Serializable) src); - assertEquals(src, ConsulRegistryUtils.deserialize(serialized)); + assertEquals(src, ConsulRegistryUtils.deserialize(serialized, filter)); final String encoded = ConsulRegistryUtils.encodeBase64(serialized); assertEquals("rO0ABXNyABpqYXZhLnV0aWwuQXJyYXlzJEFycmF5TGlzdNmkPL7NiAbSAgABWwABYXQAE1tMamF2YS9sYW5nL09iamVjdDt4" + "cHVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAN0AANvbmV0AAbQtNCy0LB0AAR0xZlp", @@ -53,7 +54,7 @@ public void encodeDecode() { -48, -76, -48, -78, -48, -80, 116, 0, 4, 116, -59, -103, 105 }, decoded); - assertEquals(src, ConsulRegistryUtils.deserialize(decoded)); + assertEquals(src, ConsulRegistryUtils.deserialize(decoded, filter)); } }
4e3f709b97aeCAMEL-23029 - Camel-Consul: Add ObjectInputFilter String pattern parameter in ConsulRegistry to be used in deserialize operations (#21531)
2 files changed · +45 −11
components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulRegistry.java+42 −9 modified@@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -52,6 +53,7 @@ public class ConsulRegistry implements Registry { private int port = 8500; private Consul consul; private KeyValueClient kvClient; + private String deserializationFilter = "java.**;org.apache.camel.**;!*"; /* constructor with default port */ public ConsulRegistry(String hostname) { @@ -70,6 +72,9 @@ private ConsulRegistry(Builder builder) { this.hostname = builder.hostname; this.port = builder.port; this.consul = Consul.builder().withUrl("http://" + this.hostname + ":" + this.port).build(); + if (builder.deserializationFilter != null) { + this.deserializationFilter = builder.deserializationFilter; + } } @Override @@ -80,7 +85,7 @@ public Object lookupByName(String key) { return kvClient.getValueAsString(key).map(result -> { byte[] postDecodedValue = ConsulRegistryUtils.decodeBase64(result); - return ConsulRegistryUtils.deserialize(postDecodedValue); + return ConsulRegistryUtils.deserialize(postDecodedValue, deserializationFilter); }).orElse(null); } @@ -219,7 +224,7 @@ public void put(String key, Object object) { if (lookupByName(key) != null) { remove(key); } - Object clone = ConsulRegistryUtils.clone((Serializable) object); + Object clone = ConsulRegistryUtils.clone((Serializable) object, deserializationFilter); byte[] serializedObject = ConsulRegistryUtils.serialize((Serializable) clone); // pre-encode due native encoding issues String value = ConsulRegistryUtils.encodeBase64(serializedObject); @@ -239,6 +244,7 @@ public static class Builder { String hostname; // optional parameter Integer port = 8500; + String deserializationFilter; public Builder(String hostname) { this.hostname = hostname; @@ -249,6 +255,11 @@ public Builder port(Integer port) { return this; } + public Builder deserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + return this; + } + public ConsulRegistry build() { return new ConsulRegistry(this); } @@ -270,6 +281,23 @@ public void setPort(int port) { this.port = port; } + /** + * Gets the deserialization filter applied when reading objects from Consul KV store. + */ + public String getDeserializationFilter() { + return deserializationFilter; + } + + /** + * Sets a deserialization filter while reading objects from Consul KV store. By default the filter will allow all + * java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be + * blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be + * deserialized. + */ + public void setDeserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + } + static final class ConsulRegistryUtils { private ConsulRegistryUtils() { @@ -300,11 +328,15 @@ static String encodeBase64(final byte[] binaryData) { /** * Deserializes an object out of the given byte array. * - * @param bytes the byte array to deserialize from - * @return an {@link Object} deserialized from the given byte array + * @param bytes the byte array to deserialize from + * @param deserializationFilter the deserialization filter to apply (e.g. "java.**;org.apache.camel.**;!*") + * @return an {@link Object} deserialized from the given byte array */ - static Object deserialize(byte[] bytes) { + static Object deserialize(byte[] bytes, String deserializationFilter) { try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) { + if (deserializationFilter != null && !deserializationFilter.isEmpty()) { + in.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter)); + } return in.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException(e); @@ -314,11 +346,12 @@ static Object deserialize(byte[] bytes) { /** * A deep serialization based clone * - * @param object the object to clone - * @return a deep clone + * @param object the object to clone + * @param deserializationFilter the deserialization filter to apply + * @return a deep clone */ - static Object clone(Serializable object) { - return deserialize(serialize(object)); + static Object clone(Serializable object, String deserializationFilter) { + return deserialize(serialize(object), deserializationFilter); } /**
components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulRegistryUtilsTest.java+3 −2 modified@@ -33,9 +33,10 @@ public class ConsulRegistryUtilsTest { @Test public void encodeDecode() { + final String filter = "java.**;org.apache.camel.**;!*"; final List<String> src = Arrays.asList("one", "\u0434\u0432\u0430", "t\u0159i"); final byte[] serialized = ConsulRegistryUtils.serialize((Serializable) src); - assertEquals(src, ConsulRegistryUtils.deserialize(serialized)); + assertEquals(src, ConsulRegistryUtils.deserialize(serialized, filter)); final String encoded = ConsulRegistryUtils.encodeBase64(serialized); assertEquals("rO0ABXNyABpqYXZhLnV0aWwuQXJyYXlzJEFycmF5TGlzdNmkPL7NiAbSAgABWwABYXQAE1tMamF2YS9sYW5nL09iamVjdDt4" + "cHVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAN0AANvbmV0AAbQtNCy0LB0AAR0xZlp", @@ -53,7 +54,7 @@ public void encodeDecode() { -48, -76, -48, -78, -48, -80, 116, 0, 4, 116, -59, -103, 105 }, decoded); - assertEquals(src, ConsulRegistryUtils.deserialize(decoded)); + assertEquals(src, ConsulRegistryUtils.deserialize(decoded, filter)); } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- camel.apache.org/security/CVE-2026-27172.htmlnvdVendor AdvisoryWEB
- github.com/advisories/GHSA-5rc6-9qfp-8vwgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-27172ghsaADVISORY
- github.com/apache/camel/commit/4b540e6e20bad4a4af19688b85a247bdb96c2e2dghsaWEB
- github.com/apache/camel/commit/4e3f709b97aef3ed99e3a52a99c752b37b104063ghsaWEB
- github.com/apache/camel/commit/55dd9f8ce5f6db06f3946c3f3df1e2ea16e4f374ghsaWEB
- github.com/apache/camel/pull/21530ghsaWEB
- github.com/apache/camel/pull/21531ghsaWEB
- github.com/apache/camel/pull/21532ghsaWEB
- issues.apache.org/jira/browse/CAMEL-23029ghsaWEB
News mentions
0No linked articles in our index yet.