From b105e2cdae21466868565bf46225ee96853f526c Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Mon, 26 Jan 2026 17:54:49 +0800 Subject: [PATCH 1/5] sec(store, server): disable remote access for arthasstart --- .../hugegraph/config/ServerOptions.java | 2 +- .../hugegraph/store/node/AppConfig.java | 4 ++-- .../store/node/controller/PartitionAPI.java | 20 +++++++++++++++++++ .../src/main/resources/application.yml | 7 +++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java index 920d119d45..8a1c3c0208 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java @@ -439,7 +439,7 @@ public class ServerOptions extends OptionHolder { "arthas.ip", "arthas bound ip", disallowEmpty(), - "0.0.0.0" + "127.0.0.1" ); public static final ConfigOption ARTHAS_DISABLED_COMMANDS = diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java index 3f1624c087..b1183d0f55 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java @@ -211,10 +211,10 @@ public class ArthasConfig { @Value("${arthas.httpPort:8565}") private String httpPort; - @Value("${arthas.ip:0.0.0.0}") + @Value("${arthas.ip:127.0.0.1}") private String arthasip; - @Value("${arthas.disabledCommands:jad}") + @Value("${arthas.disabledCommands:jad,ognl,vmtool}") private String disCmd; } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java index 34f03642ed..228b56bc46 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.Metapb; @@ -44,6 +45,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.util.Endpoint; @@ -189,6 +192,16 @@ public Map cleanPartition(@PathVariable(value = "id") int id) th @GetMapping(value = "/arthasstart", produces = "application/json") public Map arthasstart( @RequestParam(required = false, defaultValue = "") String flags) { + String remoteAddr = ((ServletRequestAttributes) Objects.requireNonNull( + RequestContextHolder.getRequestAttributes())).getRequest().getRemoteAddr(); + + boolean isLocalRequest = "127.0.0.1".equals(remoteAddr) || + "[0:0:0:0:0:0:0:1]".equals(remoteAddr); + if (!isLocalRequest){ + List ret = new ArrayList<>(); + ret.add("Arthas start is ONLY allowed from localhost."); + return forbiddenMap("arthasstart", ret); + } HashMap configMap = new HashMap<>(); configMap.put("arthas.telnetPort", appConfig.getArthasConfig().getTelnetPort()); configMap.put("arthas.httpPort", appConfig.getArthasConfig().getHttpPort()); @@ -225,6 +238,13 @@ public Map okMap(String k, Object v) { return map; } + public Map forbiddenMap(String k, Object v){ + HashMap map = new HashMap<>(); + map.put("status", 403); + map.put(k,v); + return map; + } + @Data public class Raft { diff --git a/hugegraph-store/hg-store-node/src/main/resources/application.yml b/hugegraph-store/hg-store-node/src/main/resources/application.yml index 0b65270608..778af90b87 100644 --- a/hugegraph-store/hg-store-node/src/main/resources/application.yml +++ b/hugegraph-store/hg-store-node/src/main/resources/application.yml @@ -49,3 +49,10 @@ logging: config: classpath:log4j2-dev.xml level: root: info + +arthas: + telnetPort: 8566 + httpPort: 8565 + # Only allow starting arthas locally + ip: 127.0.0.1 + disabledCommands: jad,ognl,vmtool From bda5d5211756c479270d0bc8bb0d7dab3e6b26f5 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Tue, 27 Jan 2026 17:27:56 +0800 Subject: [PATCH 2/5] fix(server): arthas configuration is ignored due to inconsistent naming pattern --- .../java/org/apache/hugegraph/config/ServerOptions.java | 2 +- .../src/assembly/static/conf/rest-server.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java index 8a1c3c0208..4aee74799f 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java @@ -447,7 +447,7 @@ public class ServerOptions extends OptionHolder { "arthas.disabledCommands", "arthas disabled commands", disallowEmpty(), - "jad" + "jad,ognl,vmtool" ); public static final ConfigOption ALLOW_TRACE = diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties index 0dce972719..e4ddf0db97 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties @@ -12,10 +12,10 @@ batch.max_write_ratio=80 batch.max_write_threads=0 # configuration of arthas -arthas.telnet_port=8562 -arthas.http_port=8561 +arthas.telnetPort=8562 +arthas.httpPort=8561 arthas.ip=127.0.0.1 -arthas.disabled_commands=jad +arthas.disabledCommands=jad,ognl,vmtool # authentication configs #auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator From 339117d103461c5cd4cf2fd76d7a7e92cdce5d02 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 29 Jan 2026 15:44:16 +0800 Subject: [PATCH 3/5] fix: unify the return value and block non-web request --- .../store/node/controller/PartitionAPI.java | 80 +++++++++++++------ 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java index 228b56bc46..1879fdeebe 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java @@ -22,7 +22,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.Metapb; @@ -38,15 +39,15 @@ import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; import org.apache.hugegraph.util.Bytes; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.util.Endpoint; @@ -67,7 +68,7 @@ public class PartitionAPI { AppConfig appConfig; @GetMapping(value = "/partitions", produces = "application/json") - public Map getPartitions( + public ResponseEntity> getPartitions( @RequestParam(required = false, defaultValue = "") String flags) { boolean accurate = false; @@ -112,7 +113,7 @@ public Map getPartitions( rafts.add(raft); } - return okMap("partitions", rafts); + return ResponseEntity.status(HttpStatus.OK).body(okMap("partitions", rafts)); } @GetMapping(value = "/partition/{id}", produces = "application/json") @@ -149,8 +150,8 @@ public Raft getPartition(@PathVariable(value = "id") int id) { * Print all keys in the partition */ @GetMapping(value = "/partition/dump/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public Map dumpPartition(@PathVariable(value = "id") int id) throws - PDException { + public ResponseEntity> dumpPartition( + @PathVariable(value = "id") int id) throws PDException { HgStoreEngine storeEngine = nodeService.getStoreEngine(); BusinessHandler handler = storeEngine.getBusinessHandler(); InnerKeyCreator innerKeyCreator = new InnerKeyCreator(handler); @@ -171,36 +172,44 @@ public Map dumpPartition(@PathVariable(value = "id") int id) thr } cfIterator.close(); }); - return okMap("ok", null); + return ResponseEntity.status(HttpStatus.OK).body(okMap("ok", null)); } /** * Print all keys in the partition */ @GetMapping(value = "/partition/clean/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public Map cleanPartition(@PathVariable(value = "id") int id) throws - PDException { + public ResponseEntity> cleanPartition( + @PathVariable(value = "id") int id) throws PDException { HgStoreEngine storeEngine = nodeService.getStoreEngine(); BusinessHandler handler = storeEngine.getBusinessHandler(); storeEngine.getPartitionEngine(id).getPartitions().forEach((graph, partition) -> { handler.cleanPartition(graph, id); }); - return okMap("ok", null); + return ResponseEntity.status(HttpStatus.OK).body(okMap("ok", null)); } @GetMapping(value = "/arthasstart", produces = "application/json") - public Map arthasstart( - @RequestParam(required = false, defaultValue = "") String flags) { - String remoteAddr = ((ServletRequestAttributes) Objects.requireNonNull( - RequestContextHolder.getRequestAttributes())).getRequest().getRemoteAddr(); - - boolean isLocalRequest = "127.0.0.1".equals(remoteAddr) || - "[0:0:0:0:0:0:0:1]".equals(remoteAddr); - if (!isLocalRequest){ + public ResponseEntity> arthasstart( + @RequestParam(required = false, defaultValue = "") String flags, + HttpServletRequest request) { + if (request == null) { + log.error("Security Alert: HttpServletRequest is NULL when accessing /arthasstart. " + + "Access denied."); + Map err = new HashMap<>(); + err.put("status", "ERROR"); + err.put("message", "Internal Error: Cannot determine client IP."); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(err); + } + String remoteAddr = getClientIp(request); + boolean isLocalRequest = + "127.0.0.1".equals(remoteAddr) || "[0:0:0:0:0:0:0:1]".equals(remoteAddr); + if (!isLocalRequest) { List ret = new ArrayList<>(); ret.add("Arthas start is ONLY allowed from localhost."); - return forbiddenMap("arthasstart", ret); + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(forbiddenMap("arthasstart", ret)); } HashMap configMap = new HashMap<>(); configMap.put("arthas.telnetPort", appConfig.getArthasConfig().getTelnetPort()); @@ -211,11 +220,11 @@ public Map arthasstart( // DashResponse retPose = new DashResponse(); List ret = new ArrayList<>(); ret.add("Arthas started successfully"); - return okMap("arthasstart", ret); + return ResponseEntity.status(HttpStatus.OK).body(okMap("arthasstart", ret)); } @PostMapping("/compat") - public Map compact(@RequestParam(value = "id") int id) { + public ResponseEntity> compact(@RequestParam(value = "id") int id) { boolean submitted = nodeService.getStoreEngine().getBusinessHandler().blockingCompact("", id); Map map = new HashMap<>(); @@ -228,7 +237,7 @@ public Map compact(@RequestParam(value = "id") int id) { map.put("msg", "compaction task fail to submit, and there could be another task in progress"); } - return map; + return ResponseEntity.status(HttpStatus.OK).body(map); } public Map okMap(String k, Object v) { @@ -238,13 +247,34 @@ public Map okMap(String k, Object v) { return map; } - public Map forbiddenMap(String k, Object v){ + public Map forbiddenMap(String k, Object v) { HashMap map = new HashMap<>(); map.put("status", 403); - map.put(k,v); + map.put(k, v); return map; } + private String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + return ip; + } + @Data public class Raft { From 5e5063dfeba2394057bbf32aa6bbc201e1ac4c27 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 29 Jan 2026 18:05:13 +0800 Subject: [PATCH 4/5] fix: unify the return value and block non-web request --- .../store/node/controller/PartitionAPI.java | 76 +++++++------------ 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java index 1879fdeebe..94e065bcb2 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java @@ -113,11 +113,11 @@ public ResponseEntity> getPartitions( rafts.add(raft); } - return ResponseEntity.status(HttpStatus.OK).body(okMap("partitions", rafts)); + return ok("partitions", rafts); } @GetMapping(value = "/partition/{id}", produces = "application/json") - public Raft getPartition(@PathVariable(value = "id") int id) { + public ResponseEntity> getPartition(@PathVariable(value = "id") int id) { HgStoreEngine storeEngine = nodeService.getStoreEngine(); @@ -142,8 +142,7 @@ public Raft getPartition(@PathVariable(value = "id") int id) { raft.getPartitions().add(partition); } - return raft; - //return okMap("partition", rafts); + return ok("raft", raft); } /** @@ -172,7 +171,7 @@ public ResponseEntity> dumpPartition( } cfIterator.close(); }); - return ResponseEntity.status(HttpStatus.OK).body(okMap("ok", null)); + return ok("ok", null); } /** @@ -187,29 +186,24 @@ public ResponseEntity> cleanPartition( storeEngine.getPartitionEngine(id).getPartitions().forEach((graph, partition) -> { handler.cleanPartition(graph, id); }); - return ResponseEntity.status(HttpStatus.OK).body(okMap("ok", null)); + return ok("ok", null); } @GetMapping(value = "/arthasstart", produces = "application/json") public ResponseEntity> arthasstart( @RequestParam(required = false, defaultValue = "") String flags, HttpServletRequest request) { - if (request == null) { - log.error("Security Alert: HttpServletRequest is NULL when accessing /arthasstart. " + - "Access denied."); - Map err = new HashMap<>(); - err.put("status", "ERROR"); - err.put("message", "Internal Error: Cannot determine client IP."); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(err); - } - String remoteAddr = getClientIp(request); + // Ignore proxy headers to prevent IP spoofing. + // NOTE: If behind a reverse proxy (e.g., Nginx), getRemoteAddr() returns the proxy's IP. + // Ensure the proxy is configured to block untrusted external access. + String remoteAddr = getCleanIp(request.getRemoteAddr()); boolean isLocalRequest = - "127.0.0.1".equals(remoteAddr) || "[0:0:0:0:0:0:0:1]".equals(remoteAddr); + "127.0.0.1".equals(remoteAddr) || "0:0:0:0:0:0:0:1".equals(remoteAddr) || + "::1".equals(remoteAddr); if (!isLocalRequest) { List ret = new ArrayList<>(); ret.add("Arthas start is ONLY allowed from localhost."); - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(forbiddenMap("arthasstart", ret)); + return forbidden("arthasstart", ret); } HashMap configMap = new HashMap<>(); configMap.put("arthas.telnetPort", appConfig.getArthasConfig().getTelnetPort()); @@ -220,7 +214,7 @@ public ResponseEntity> arthasstart( // DashResponse retPose = new DashResponse(); List ret = new ArrayList<>(); ret.add("Arthas started successfully"); - return ResponseEntity.status(HttpStatus.OK).body(okMap("arthasstart", ret)); + return ok("arthasstart", ret); } @PostMapping("/compat") @@ -237,42 +231,28 @@ public ResponseEntity> compact(@RequestParam(value = "id") i map.put("msg", "compaction task fail to submit, and there could be another task in progress"); } - return ResponseEntity.status(HttpStatus.OK).body(map); + return ok("body", map); } - public Map okMap(String k, Object v) { - Map map = new HashMap<>(); - map.put("status", 0); - map.put(k, v); - return map; + public ResponseEntity> ok(String k, Object v) { + return ResponseEntity.ok(Map.of("status", 200, k, v)); } - public Map forbiddenMap(String k, Object v) { - HashMap map = new HashMap<>(); - map.put("status", 403); - map.put(k, v); - return map; + public ResponseEntity> forbidden(String k, Object v) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(Map.of("status", 403, k, v)); } - private String getClientIp(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("X-Real-IP"); - } - if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - - if (ip != null && ip.contains(",")) { - ip = ip.split(",")[0].trim(); + private String getCleanIp(String remoteAddr) { + if (remoteAddr != null) { + if (remoteAddr.startsWith("[")) { + remoteAddr = remoteAddr.substring(1); + } + if (remoteAddr.endsWith("]")) { + remoteAddr = remoteAddr.substring(0, remoteAddr.length() - 1); + } } - return ip; + return remoteAddr; } @Data From a4abb3707513035ea616926b8de66fbfa671897a Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 29 Jan 2026 18:33:41 +0800 Subject: [PATCH 5/5] trigger-ci