CVE-2023-45860
Description
In Hazelcast Platform through 5.3.4, the CSV File Source connector's SQL mapping lacks proper permission checks, allowing unauthorized clients to read files from a member's filesystem.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In Hazelcast Platform through 5.3.4, the CSV File Source connector's SQL mapping lacks proper permission checks, allowing unauthorized clients to read files from a member's filesystem.
Vulnerability
Overview
The CSV File Source connector in Hazelcast Platform versions up to and including 5.3.4 contains a security flaw in its SQL mapping feature. The root cause is inadequate permission checking within the SqlConnector class, specifically in the resolveAndValidateFields method, which attempts to resolve field names from metadata and a sample file without verifying that the requesting client has the necessary permissions [1]. This oversight allows an unauthorized client to create a SQL mapping for a CSV file and subsequently read arbitrary files from the member's filesystem.
Exploitation
Vector
Exploitation requires network access to a Hazelcast cluster with the CSV File Source connector enabled. The attacker does not need prior authentication or elevated privileges because the permission check is missing during the mapping creation process. By crafting a malicious SQL mapping that references a file path on the server, the attacker can trigger the connector to read and expose the file's contents [3]. The lack of a permissionsForResolve method meant that the resolveAndValidateFields operation was executed without any security context, as highlighted in the fix commit [4].
Impact
Successful exploitation results in unauthorized file disclosure, potentially exposing sensitive data such as configuration files, credentials, or business-critical information. The impact is limited to file read operations; the attacker cannot modify files or execute code. However, the confidentiality of the system is compromised, and the vulnerability can be leveraged to gather intelligence for further attacks.
Mitigation
The issue has been addressed in later versions of Hazelcast Platform. The fix, introduced in pull request #25348, adds a new method SqlConnector#permissionsForResolve that returns the required permissions and propagates a SqlSecurityContext to enforce the check [3][4]. Users are strongly advised to upgrade to a patched release (e.g., 5.3.5 or later) and apply the corresponding security update. As of the publication date, no workaround is documented, but restricting network access to the cluster can reduce the attack surface.
AI Insight generated on May 20, 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.
| Package | Affected versions | Patched versions |
|---|---|---|
com.hazelcast:hazelcastMaven | >= 5.3.0, < 5.3.5 | 5.3.5 |
com.hazelcast:hazelcast-enterpriseMaven | >= 5.3.0, < 5.3.5 | 5.3.5 |
com.hazelcast:hazelcast-enterpriseMaven | >= 5.2.0, < 5.2.5 | 5.2.5 |
com.hazelcast:hazelcast-enterpriseMaven | <= 5.1.7 | — |
com.hazelcast:hazelcastMaven | >= 5.2.0, < 5.2.5 | 5.2.5 |
com.hazelcast:hazelcastMaven | <= 5.1.7 | — |
Affected products
3- Hazelcast/Platformdescription
- ghsa-coords2 versions
>= 5.3.0, < 5.3.5+ 1 more
- (no CPE)range: >= 5.3.0, < 5.3.5
- (no CPE)range: >= 5.3.0, < 5.3.5
Patches
198be233e79cfImprove permission checks in File connector [HZ-2991] (#25348)
9 files changed · +84 −22
hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/file/FileSqlConnector.java+10 −0 modified@@ -32,11 +32,15 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.security.Permission; import java.util.List; import java.util.Map; import java.util.Set; import static com.hazelcast.jet.core.Edge.between; +import static com.hazelcast.security.permission.ActionConstants.ACTION_READ; +import static com.hazelcast.security.permission.ConnectorPermission.file; +import static java.util.Collections.singletonList; public class FileSqlConnector implements SqlConnector { @@ -85,6 +89,12 @@ static List<MappingField> resolveAndValidateFields( return METADATA_RESOLVERS.resolveAndValidateFields(userFields, options); } + @Nonnull + @Override + public List<Permission> permissionsForResolve(SqlExternalResource resource, NodeEngine nodeEngine) { + return singletonList(file(resource.options().get(OPTION_PATH), ACTION_READ)); + } + @Nonnull @Override public Table createTable(
hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/SqlConnector.java+18 −0 modified@@ -39,6 +39,7 @@ import javax.annotation.Nullable; import java.io.Serializable; import java.lang.reflect.Method; +import java.security.Permission; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -47,6 +48,7 @@ import java.util.Set; import java.util.function.Consumer; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; @@ -258,6 +260,22 @@ List<MappingField> resolveAndValidateFields( @Nonnull List<MappingField> userFields ); + /** + * Returns the required permissions to execute + * {@link #resolveAndValidateFields(NodeEngine, SqlExternalResource, List)} method. + * <p> + * Implementors of {@link SqlConnector} don't need to override this method when {@code resolveAndValidateFields} + * doesn't support field resolution or when validation doesn't access the external resource. + * <p> + * The permissions are usually the same as required permissions to read from the external resource. + * + * @return list of permissions required to run {@link #resolveAndValidateFields} + */ + @Nonnull + default List<Permission> permissionsForResolve(SqlExternalResource resource, NodeEngine nodeEngine) { + return emptyList(); + } + /** * Creates a {@link Table} object with the given fields. Should return * quickly; specifically it should not attempt to connect to the remote
hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/PlanExecutor.java+2 −2 modified@@ -182,8 +182,8 @@ public PlanExecutor( logger = nodeEngine.getLogger(getClass()); } - SqlResult execute(CreateMappingPlan plan) { - catalog.createMapping(plan.mapping(), plan.replace(), plan.ifNotExists()); + SqlResult execute(CreateMappingPlan plan, SqlSecurityContext ssc) { + catalog.createMapping(plan.mapping(), plan.replace(), plan.ifNotExists(), ssc); return UpdateSqlResultImpl.createUpdateCountResult(0); }
hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/schema/TableResolverImpl.java+20 −9 modified@@ -41,8 +41,10 @@ import com.hazelcast.sql.impl.schema.dataconnection.DataConnectionCatalogEntry; import com.hazelcast.sql.impl.schema.type.Type; import com.hazelcast.sql.impl.schema.view.View; +import com.hazelcast.sql.impl.security.SqlSecurityContext; import javax.annotation.Nonnull; +import java.security.Permission; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -128,8 +130,8 @@ public void entryRemoved(EntryEvent<String, Object> event) { // region mapping - public void createMapping(Mapping mapping, boolean replace, boolean ifNotExists) { - Mapping resolved = resolveMapping(mapping); + public void createMapping(Mapping mapping, boolean replace, boolean ifNotExists, SqlSecurityContext securityContext) { + Mapping resolved = resolveMapping(mapping, securityContext); String name = resolved.name(); if (ifNotExists) { @@ -142,7 +144,7 @@ public void createMapping(Mapping mapping, boolean replace, boolean ifNotExists) } } - private Mapping resolveMapping(Mapping mapping) { + private Mapping resolveMapping(Mapping mapping, SqlSecurityContext securityContext) { Map<String, String> options = mapping.options(); String type = mapping.connectorType(); String dataConnection = mapping.dataConnection(); @@ -158,14 +160,23 @@ private Mapping resolveMapping(Mapping mapping) { ? connector.defaultObjectType() : mapping.objectType(); checkNotNull(objectType, "objectType cannot be null"); + + SqlExternalResource externalResource = new SqlExternalResource( + mapping.externalName(), + mapping.dataConnection(), + connector.typeName(), + objectType, + options + ); + + List<Permission> permissions = connector.permissionsForResolve(externalResource, nodeEngine); + for (Permission permission : permissions) { + securityContext.checkPermission(permission); + } + resolvedFields = connector.resolveAndValidateFields( nodeEngine, - new SqlExternalResource( - mapping.externalName(), - mapping.dataConnection(), - connector.typeName(), - objectType, - options), + externalResource, mapping.fields() );
hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/SqlPlanImpl.java+1 −1 modified@@ -152,7 +152,7 @@ public boolean producesRows() { public SqlResult execute(QueryId queryId, List<Object> arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE MAPPING", arguments); SqlPlanImpl.ensureNoTimeout("CREATE MAPPING", timeout); - return planExecutor.execute(this); + return planExecutor.execute(this, ssc); } }
hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/PlanExecutorTest.java+2 −2 modified@@ -112,11 +112,11 @@ public void test_createMappingExecution(boolean replace, boolean ifNotExists) { CreateMappingPlan plan = new CreateMappingPlan(planKey(), mapping, replace, ifNotExists, planExecutor); // when - SqlResult result = planExecutor.execute(plan); + SqlResult result = planExecutor.execute(plan, null); // then assertThat(result.updateCount()).isEqualTo(0); - verify(catalog).createMapping(mapping, replace, ifNotExists); + verify(catalog).createMapping(mapping, replace, ifNotExists, null); } @Test
hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/schema/TableResolverImplTest.java+5 −5 modified@@ -118,7 +118,7 @@ public void when_createsInvalidMapping_then_throws() { // when // then - assertThatThrownBy(() -> catalog.createMapping(mapping, true, true)) + assertThatThrownBy(() -> catalog.createMapping(mapping, true, true, null)) .hasMessageContaining("expected test exception"); verify(relationsStorage, never()).putIfAbsent(anyString(), (Mapping) any()); verify(relationsStorage, never()).put(anyString(), (Mapping) any()); @@ -142,7 +142,7 @@ public void when_createsDuplicateMapping_then_throws() { // when // then - assertThatThrownBy(() -> catalog.createMapping(mapping, false, false)) + assertThatThrownBy(() -> catalog.createMapping(mapping, false, false, null)) .isInstanceOf(QueryException.class) .hasMessageContaining("Mapping or view already exists: name"); verifyNoInteractions(listener); @@ -164,7 +164,7 @@ public void when_createsDuplicateMappingWithIfNotExists_then_succeeds() { given(relationsStorage.putIfAbsent(eq(mapping.name()), isA(Mapping.class))).willReturn(false); // when - catalog.createMapping(mapping, false, true); + catalog.createMapping(mapping, false, true, null); // then verifyNoInteractions(listener); @@ -185,7 +185,7 @@ public void when_replacesMapping_then_succeeds() { .willReturn(singletonList(new MappingField("field_name", INT))); // when - catalog.createMapping(mapping, true, false); + catalog.createMapping(mapping, true, false, null); // then verify(relationsStorage).put(eq(mapping.name()), isA(Mapping.class)); @@ -213,7 +213,7 @@ public void when_mappingWithNoObjectType_then_usesDefault() { given(connector.defaultObjectType()).willReturn("MyDummyType"); // when - catalog.createMapping(mapping, true, false); + catalog.createMapping(mapping, true, false, null); // then verify(relationsStorage).put(eq(mapping.name()), isA(Mapping.class));
hazelcast/src/main/java/com/hazelcast/jet/impl/connector/ReadFilesP.java+5 −3 modified@@ -28,6 +28,7 @@ import com.hazelcast.jet.pipeline.file.impl.FileTraverser; import com.hazelcast.logging.ILogger; import com.hazelcast.logging.Logger; +import com.hazelcast.security.impl.function.SecuredFunctions; import com.hazelcast.security.permission.ConnectorPermission; import javax.annotation.Nonnull; @@ -74,7 +75,7 @@ public final class ReadFilesP<T> extends AbstractProcessor { private LocalFileTraverser<T> traverser; - private ReadFilesP( + public ReadFilesP( @Nonnull String directory, @Nonnull String glob, boolean sharedFileSystem, @@ -174,8 +175,9 @@ private MetaSupplier( @Nonnull @Override public Function<? super Address, ? extends ProcessorSupplier> get(@Nonnull List<Address> addresses) { - return address -> ProcessorSupplier.of(() -> new ReadFilesP<>(directory, glob, sharedFileSystem, - ignoreFileNotFound, readFileFn)); + return address -> ProcessorSupplier.of(SecuredFunctions.readFilesProcessorFn( + directory, glob, sharedFileSystem, ignoreFileNotFound, readFileFn + )); } @Override
hazelcast/src/main/java/com/hazelcast/security/impl/function/SecuredFunctions.java+21 −0 modified@@ -27,6 +27,7 @@ import com.hazelcast.jet.core.ProcessorSupplier; import com.hazelcast.jet.core.ProcessorSupplier.Context; import com.hazelcast.jet.function.ToResultSetFunction; +import com.hazelcast.jet.impl.connector.ReadFilesP; import com.hazelcast.jet.impl.connector.ReadIListP; import com.hazelcast.jet.impl.connector.ReadJdbcP; import com.hazelcast.jet.impl.connector.StreamFilesP; @@ -220,6 +221,26 @@ public List<Permission> permissions() { }; } + public static <T> SupplierEx<Processor> readFilesProcessorFn( + String directory, + String glob, + boolean sharedFileSystem, + boolean ignoreFileNotFound, + FunctionEx<? super Path, ? extends Stream<T>> readFileFn) { + + return new SupplierEx<>() { + @Override + public Processor getEx() { + return new ReadFilesP<>(directory, glob, sharedFileSystem, ignoreFileNotFound, readFileFn); + } + + @Override + public List<Permission> permissions() { + return singletonList(ConnectorPermission.file(directory, ACTION_READ)); + } + }; + } + public static <T> FunctionEx<? super Path, ? extends Stream<T>> jsonReadFileFn( String directory, Class<T> type
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-8h4x-xvjp-vf99ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-45860ghsaADVISORY
- github.com/hazelcast/hazelcast/commit/98be233e79cf4bc1ff3c7126a9189988bd0e87bdghsaWEB
- github.com/hazelcast/hazelcast/pull/25348ghsaWEB
- github.com/hazelcast/hazelcast/security/advisories/GHSA-8h4x-xvjp-vf99ghsaWEB
News mentions
0No linked articles in our index yet.