This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 2199def5 [ISSUE #1509] Block cloud metadata endpoints missed by Java
address checks (#1510)
2199def5 is described below
commit 2199def5905de7771b9ca0ceb3f7e91b1194e5a9
Author: youngkermit8-coder <[email protected]>
AuthorDate: Tue Aug 11 20:19:40 2026 +0800
[ISSUE #1509] Block cloud metadata endpoints missed by Java address checks
(#1510)
* [ISSUE #1509] Block the AWS IMDS IPv6 endpoint
Signed-off-by: youngkermit8-coder <[email protected]>
* fix(security): block Alibaba Cloud metadata endpoint
Signed-off-by: youngkermit8-coder <[email protected]>
* test(security): cover IPv4-mapped metadata address
Signed-off-by: youngkermit8-coder <[email protected]>
---------
Signed-off-by: youngkermit8-coder <[email protected]>
---
.../rocketmq/studio/settings/SettingsService.java | 28 +++++++++++----
.../studio/settings/SettingsServiceTest.java | 40 ++++++++++++++++++++++
2 files changed, 62 insertions(+), 6 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
index ba1744e0..80873331 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
@@ -39,6 +39,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.time.Duration;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -48,6 +49,14 @@ import java.util.UUID;
@Service
public class SettingsService {
+ private static final List<byte[]> CLOUD_METADATA_ADDRESSES = List.of(
+ new byte[] {
+ (byte) 0xfd, 0x00, 0x0e, (byte) 0xc2,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x54
+ }, // AWS IMDS IPv6: fd00:ec2::254
+ new byte[] {100, 100, 100, (byte) 200}); // Alibaba Cloud ECS
metadata
private static final Set<String> PROMETHEUS_COMPATIBLE_TYPES = Set.of(
"prometheus", "victoriametrics", "thanos", "mimir", "cortex",
"arms");
private static final String PROMETHEUS_TEST_QUERY = "up";
@@ -251,11 +260,12 @@ public class SettingsService {
/**
* SSRF guard: the test endpoint performs a server-side HTTP request to an
attacker-supplied
- * URL. The hostname {@code localhost}, loopback IPs (127.x.x.x, ::1) and
link-local addresses
- * (169.254.x.x, fe80:: — the cloud metadata range) are never legitimate
Prometheus endpoints
- * and are rejected. Private site-local ranges stay allowed because
on-premise Prometheus
- * servers live on the internal network and the endpoint itself requires
admin rights.
- * Package-private so tests can admit the loopback-bound embedded test
server.
+ * URL. The hostname {@code localhost}, loopback IPs (127.x.x.x, ::1),
link-local addresses
+ * (169.254.x.x, fe80:: — the cloud metadata range), and known metadata
endpoints not covered
+ * by Java's address categories are never legitimate Prometheus endpoints
and are rejected.
+ * Private site-local ranges stay allowed because on-premise Prometheus
servers live on the
+ * internal network and the endpoint itself requires admin rights.
Package-private so tests
+ * can admit the loopback-bound embedded test server.
*/
boolean isAllowedDataSourceHost(String host) {
if (!StringUtils.hasText(host)) {
@@ -269,13 +279,19 @@ public class SettingsService {
InetAddress address = InetAddress.getByName(normalized);
return !address.isAnyLocalAddress()
&& !address.isLinkLocalAddress()
- && !address.isLoopbackAddress();
+ && !address.isLoopbackAddress()
+ && !isKnownCloudMetadataAddress(address);
} catch (UnknownHostException exception) {
// Unresolvable host: let the connection attempt surface the real
connectivity error.
return true;
}
}
+ private boolean isKnownCloudMetadataAddress(InetAddress address) {
+ return CLOUD_METADATA_ADDRESSES.stream()
+ .anyMatch(metadataAddress ->
Arrays.equals(address.getAddress(), metadataAddress));
+ }
+
private DataSourceTestResultVO prometheusSuccess(JsonNode response) {
if (response != null &&
"success".equals(response.path("status").asText())) {
return DataSourceTestResultVO.builder()
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
index 0b264c72..ee111fcf 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
@@ -460,6 +460,46 @@ class SettingsServiceTest {
assertThat(result.getMessage()).contains("local or private address");
}
+ @Test
+ void testConnectionShouldRejectAwsImdsIpv6Address() {
+ DataSourceTestDTO compressedRequest = DataSourceTestDTO.builder()
+ .url("http://[fd00:ec2::254]/latest/meta-data/")
+ .type("Prometheus")
+ .build();
+ DataSourceTestDTO expandedRequest = DataSourceTestDTO.builder()
+
.url("http://[fd00:0ec2:0000:0000:0000:0000:0000:0254]/latest/meta-data/")
+ .type("Prometheus")
+ .build();
+
+ DataSourceTestResultVO compressedResult =
settingsService.testDataSource(compressedRequest);
+ DataSourceTestResultVO expandedResult =
settingsService.testDataSource(expandedRequest);
+
+ assertThat(compressedResult.isSuccess()).isFalse();
+ assertThat(compressedResult.getMessage()).contains("local or private
address");
+ assertThat(expandedResult.isSuccess()).isFalse();
+ assertThat(expandedResult.getMessage()).contains("local or private
address");
+ }
+
+ @Test
+ void testConnectionShouldRejectAlibabaCloudMetadataAddress() {
+ DataSourceTestDTO request = DataSourceTestDTO.builder()
+ .url("http://100.100.100.200/latest/meta-data/")
+ .type("Prometheus")
+ .build();
+ DataSourceTestDTO ipv4MappedRequest = DataSourceTestDTO.builder()
+ .url("http://[::ffff:100.100.100.200]/latest/meta-data/")
+ .type("Prometheus")
+ .build();
+
+ DataSourceTestResultVO result =
settingsService.testDataSource(request);
+ DataSourceTestResultVO ipv4MappedResult =
settingsService.testDataSource(ipv4MappedRequest);
+
+ assertThat(result.isSuccess()).isFalse();
+ assertThat(result.getMessage()).contains("local or private address");
+ assertThat(ipv4MappedResult.isSuccess()).isFalse();
+ assertThat(ipv4MappedResult.getMessage()).contains("local or private
address");
+ }
+
@Test
void testConnectionShouldRejectIncompleteBasicAuthentication() {
DataSourceTestResultVO result =
settingsService.testDataSource(DataSourceTestDTO.builder()