CVE-2020-1937
Description
Apache Kylin REST APIs concatenate user input into SQL queries, enabling unauthenticated SQL injection attacks against the underlying database.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Kylin REST APIs concatenate user input into SQL queries, enabling unauthenticated SQL injection attacks against the underlying database.
Vulnerability
Overview
CVE-2020-1937 is a SQL injection vulnerability in Apache Kylin, an open-source OLAP engine for big data. The affected API endpoints construct SQL queries by directly concatenating user-supplied input without proper sanitization or parameterization. This flaw allows an attacker to inject arbitrary SQL commands, leading to unauthorized database access.
Exploitation
Details
Attackers can exploit this vulnerability by sending crafted HTTP requests to Kylin's RESTful services. The descriptions and references confirm that the APIs accept user-provided strings and embed them directly into SQL statements [1][3]. No authentication is explicitly required to reach these endpoints, though actual exploitability may depend on network exposure and configuration. The vulnerability affects versions before 2.6.5 and 3.0.1 for the kylin-server-base package [4].
Impact
Successful exploitation enables an attacker to execute malicious SQL queries against the database backend, potentially reading, modifying, or deleting sensitive data stored in Kylin's metadata or data cubes. Given Kylin's role in analytics pipelines, a compromise could expose aggregated datasets, user credentials, or other business-critical information.
Mitigation
The fix is implemented in Apache Kylin commits that introduced parameterized queries and input validation [1]. Administrators should upgrade to version 2.6.5, 3.0.1, or later [4]. No workarounds are documented; restricting network access to the Kylin REST API may reduce attack surface but does not eliminate the vulnerability.
AI Insight generated on May 21, 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 |
|---|---|---|
org.apache.kylin:kylin-server-baseMaven | < 2.6.5 | 2.6.5 |
org.apache.kylin:kylin-server-baseMaven | >= 3.0.0, < 3.0.1 | 3.0.1 |
Affected products
2- Apache/Apache Kylinv5Range: ApacheKylin 2.3.0 to 2.3.2
Patches
11 file changed · +51 −30
server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java+51 −30 modified@@ -71,6 +71,7 @@ import org.apache.kylin.metadata.project.RealizationEntry; import org.apache.kylin.metadata.realization.RealizationStatusEnum; import org.apache.kylin.metadata.realization.RealizationType; +import org.apache.kylin.metrics.MetricsManager; import org.apache.kylin.metrics.property.QueryCubePropertyEnum; import org.apache.kylin.rest.constant.Constant; import org.apache.kylin.rest.exception.BadRequestException; @@ -79,6 +80,7 @@ import org.apache.kylin.rest.msg.Message; import org.apache.kylin.rest.msg.MsgPicker; import org.apache.kylin.rest.request.MetricsRequest; +import org.apache.kylin.rest.request.PrepareSqlRequest; import org.apache.kylin.rest.response.CubeInstanceResponse; import org.apache.kylin.rest.response.CuboidTreeResponse; import org.apache.kylin.rest.response.CuboidTreeResponse.NodeInfo; @@ -544,7 +546,8 @@ public HBaseResponse getHTableInfo(String cubeName, String tableName) throws IOE hr = new HBaseResponse(); CubeInstance cube = CubeManager.getInstance(getConfig()).getCube(cubeName); - if (cube.getStorageType() == IStorageAware.ID_HBASE || cube.getStorageType() == IStorageAware.ID_SHARDED_HBASE || cube.getStorageType() == IStorageAware.ID_REALTIME_AND_HBASE) { + if (cube.getStorageType() == IStorageAware.ID_HBASE || cube.getStorageType() == IStorageAware.ID_SHARDED_HBASE + || cube.getStorageType() == IStorageAware.ID_REALTIME_AND_HBASE) { try { logger.debug("Loading HTable info " + cubeName + ", " + tableName); @@ -633,7 +636,8 @@ private void cleanSegmentStorage(List<CubeSegment> toRemoveSegs) throws IOExcept List<String> toDelHDFSPaths = Lists.newArrayListWithCapacity(toRemoveSegs.size()); for (CubeSegment seg : toRemoveSegs) { toDropHTables.add(seg.getStorageLocationIdentifier()); - toDelHDFSPaths.add(JobBuilderSupport.getJobWorkingDir(seg.getConfig().getHdfsWorkingDirectory(), seg.getLastBuildJobID())); + toDelHDFSPaths.add(JobBuilderSupport.getJobWorkingDir(seg.getConfig().getHdfsWorkingDirectory(), + seg.getLastBuildJobID())); } StorageCleanUtil.dropHTables(new HBaseAdmin(HBaseConnection.getCurrentHBaseConfiguration()), toDropHTables); @@ -763,10 +767,12 @@ public String mergeCubeSegment(String cubeName) { } //Don't merge the job that has been discarded manually before - private boolean isMergingJobBeenDiscarded(CubeInstance cubeInstance, String cubeName, String projectName, SegmentRange offsets) { + private boolean isMergingJobBeenDiscarded(CubeInstance cubeInstance, String cubeName, String projectName, + SegmentRange offsets) { SegmentRange.TSRange tsRange = new SegmentRange.TSRange((Long) offsets.start.v, (Long) offsets.end.v); String segmentName = CubeSegment.makeSegmentName(tsRange, null, cubeInstance.getModel()); - final List<CubingJob> jobInstanceList = jobService.listJobsByRealizationName(cubeName, projectName, EnumSet.of(ExecutableState.DISCARDED)); + final List<CubingJob> jobInstanceList = jobService.listJobsByRealizationName(cubeName, projectName, + EnumSet.of(ExecutableState.DISCARDED)); for (CubingJob cubingJob : jobInstanceList) { if (cubingJob.getSegmentName().equals(segmentName)) { logger.debug("Merge job {} has been discarded before, will not merge.", segmentName); @@ -777,7 +783,6 @@ private boolean isMergingJobBeenDiscarded(CubeInstance cubeInstance, String cube return false; } - public void validateCubeDesc(CubeDesc desc, boolean isDraft) { Message msg = MsgPicker.getMsg(); @@ -931,24 +936,6 @@ public void afterPropertiesSet() throws Exception { Broadcaster.getInstance(getConfig()).registerStaticListener(new HTableInfoSyncListener(), "cube"); } - private class HTableInfoSyncListener extends Broadcaster.Listener { - @Override - public void onClearAll(Broadcaster broadcaster) throws IOException { - htableInfoCache.invalidateAll(); - } - - @Override - public void onEntityChange(Broadcaster broadcaster, String entity, Broadcaster.Event event, String cacheKey) - throws IOException { - String cubeName = cacheKey; - String keyPrefix = cubeName + "/"; - for (String k : htableInfoCache.asMap().keySet()) { - if (k.startsWith(keyPrefix)) - htableInfoCache.invalidate(k); - } - } - } - public CubeInstanceResponse createCubeInstanceResponse(CubeInstance cube) { return new CubeInstanceResponse(cube, projectService.getProjectOfCube(cube.getName())); } @@ -995,7 +982,7 @@ private NodeInfo generateNodeInfo(long cuboidId, int dimensionCount, long cubeQu long queryExactlyMatchCount = queryMatchMap == null || queryMatchMap.get(cuboidId) == null ? 0L : queryMatchMap.get(cuboidId); boolean ifExist = currentCuboidSet.contains(cuboidId); - long rowCount = rowCountMap == null ? 0L : rowCountMap.get(cuboidId); + long rowCount = (rowCountMap == null || rowCountMap.size() == 0) ? 0L : rowCountMap.get(cuboidId); NodeInfo node = new NodeInfo(); node.setId(cuboidId); @@ -1044,9 +1031,10 @@ public Map<Long, Long> getCuboidHitFrequency(String cubeName, boolean isCuboidSo String table = getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectQueryCube()); String sql = "select " + cuboidColumn + ", sum(" + hitMeasure + ")" // + " from " + table// - + " where " + QueryCubePropertyEnum.CUBE.toString() + " = '" + cubeName + "'" // + + " where " + QueryCubePropertyEnum.CUBE.toString() + " = ?" // + " group by " + cuboidColumn; - List<List<String>> orgHitFrequency = queryService.querySystemCube(sql).getResults(); + + List<List<String>> orgHitFrequency = getPrepareQueryResult(cubeName, sql); return formatQueryCount(orgHitFrequency); } @@ -1058,9 +1046,10 @@ public Map<Long, Map<Long, Pair<Long, Long>>> getCuboidRollingUpStats(String cub String table = getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectQueryCube()); String sql = "select " + cuboidSource + ", " + cuboidTgt + ", avg(" + aggCount + "), avg(" + returnCount + ")"// + " from " + table // - + " where " + QueryCubePropertyEnum.CUBE.toString() + " = '" + cubeName + "' " // + + " where " + QueryCubePropertyEnum.CUBE.toString() + " = ?" // + " group by " + cuboidSource + ", " + cuboidTgt; - List<List<String>> orgRollingUpCount = queryService.querySystemCube(sql).getResults(); + + List<List<String>> orgRollingUpCount = getPrepareQueryResult(cubeName, sql); return formatRollingUpStats(orgRollingUpCount); } @@ -1070,13 +1059,27 @@ public Map<Long, Long> getCuboidQueryMatchCount(String cubeName) { String table = getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectQueryCube()); String sql = "select " + cuboidSource + ", sum(" + hitMeasure + ")" // + " from " + table // - + " where " + QueryCubePropertyEnum.CUBE.toString() + " = '" + cubeName + "'" // + + " where " + QueryCubePropertyEnum.CUBE.toString() + " = ?" // + " and " + QueryCubePropertyEnum.IF_MATCH.toString() + " = true" // + " group by " + cuboidSource; - List<List<String>> orgMatchHitFrequency = queryService.querySystemCube(sql).getResults(); + + List<List<String>> orgMatchHitFrequency = getPrepareQueryResult(cubeName, sql); return formatQueryCount(orgMatchHitFrequency); } + private List<List<String>> getPrepareQueryResult(String cubeName, String sql) { + PrepareSqlRequest sqlRequest = new PrepareSqlRequest(); + sqlRequest.setProject(MetricsManager.SYSTEM_PROJECT); + PrepareSqlRequest.StateParam[] params = new PrepareSqlRequest.StateParam[1]; + params[0] = new PrepareSqlRequest.StateParam(); + params[0].setClassName("java.lang.String"); + params[0].setValue(cubeName); + sqlRequest.setParams(params); + sqlRequest.setSql(sql); + + return queryService.doQueryWithCache(sqlRequest, false).getResults(); + } + @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'MANAGEMENT')") public void migrateCube(CubeInstance cube, String projectName) { @@ -1114,4 +1117,22 @@ public void migrateCube(CubeInstance cube, String projectName) { throw new InternalErrorException("Failed to perform one-click migrating", e); } } + + private class HTableInfoSyncListener extends Broadcaster.Listener { + @Override + public void onClearAll(Broadcaster broadcaster) throws IOException { + htableInfoCache.invalidateAll(); + } + + @Override + public void onEntityChange(Broadcaster broadcaster, String entity, Broadcaster.Event event, String cacheKey) + throws IOException { + String cubeName = cacheKey; + String keyPrefix = cubeName + "/"; + for (String k : htableInfoCache.asMap().keySet()) { + if (k.startsWith(keyPrefix)) + htableInfoCache.invalidate(k); + } + } + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-7hmh-8gwv-mfvqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-1937ghsaADVISORY
- github.com/apache/kylin/commit/e373c64c96a54a7abfe4bccb82e8feb60db04749ghsaWEB
- lists.apache.org/thread.html/r021baf9d8d4ae41e8c8332c167c4fa96c91b5086563d9be55d2d7acf%40%3Ccommits.kylin.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r021baf9d8d4ae41e8c8332c167c4fa96c91b5086563d9be55d2d7acf@%3Ccommits.kylin.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r61666760d8a4e8764b2d5fe158d8a48b569414480fbfadede574cdc0%40%3Ccommits.kylin.apache.org%3Emitremailing-listx_refsource_MLIST
- lists.apache.org/thread.html/r61666760d8a4e8764b2d5fe158d8a48b569414480fbfadede574cdc0@%3Ccommits.kylin.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rc574fef23740522f62ab3bbda4f6171be98aa7a25f3f54be143a80a8%40%3Cuser.kylin.apache.org%3Eghsamailing-listx_refsource_MLISTWEB
- snyk.io/vuln/SNYK-JAVA-ORGAPACHEKYLIN-552148ghsaWEB
News mentions
0No linked articles in our index yet.