Skip to content

LDAP Injection in CMAK (Cluster Manager for Apache Kafka) via Search Filter #8841

Description

@TuanHung1149

Summary

Yahoo CMAK (Cluster Manager for Apache Kafka) versions up to and including 3.0.0.6 are vulnerable to LDAP Injection (CWE-90) in the LDAP authentication module.

Vulnerability Details

File: app/controllers/BasicAuthenticationFilter.scala
Function: renderSearchFilter() at line 264
Root Cause: User-supplied username is inserted into LDAP search filter via simple string replacement (%s) without LDAP special character escaping.

Vulnerable Code (line 264):

private def renderSearchFilter(username: String): String = {
  searchFilter.replaceAll("%s", username)
}

The function passes raw user input directly into an LDAP search filter string. LDAP metacharacters such as *, (, ), \, and NUL byte are not escaped before insertion.

Attack Vectors

An attacker can inject LDAP filter syntax through the HTTP Basic Authentication username field:

  1. Wildcard bypass (*): Matches any LDAP entry, bypassing username validation
  2. Boolean logic injection (admin)(|(cn=*)): Injects OR conditions to match arbitrary entries
  3. Filter manipulation (*)(objectClass=*)): Extends filter to match all objects
  4. Attribute enumeration (*)(mail=*@yahoo.com)): Enumerates entries by attribute

Impact

  • Authentication bypass: Attacker can log in as any LDAP user without knowing the password (if LDAP bind is not configured)
  • LDAP data exfiltration: Enumeration of LDAP directory entries (usernames, emails, group memberships)
  • Authorization escalation: Access admin-level CMAK functions (topic deletion, partition reassignment, broker configuration)

Proof of Concept

A complete Docker-based PoC is available demonstrating 4 confirmed attack vectors against a live CMAK + OpenLDAP setup:

# Attack vector: wildcard authentication bypass
import requests, base64

target = "http://cmak-host:9000/"
# Normal user "admin" → 401 without correct password
# Wildcard "*" → matches all LDAP entries → bypass
cred = base64.b64encode(b"*:anything").decode()
r = requests.get(target, headers={"Authorization": f"Basic {cred}"})
# Returns 200 instead of 401

Recommended Fix

Replace renderSearchFilter with proper LDAP escaping:

import javax.naming.ldap.Rdn

private def renderSearchFilter(username: String): String = {
  searchFilter.replaceAll("%s", Rdn.escapeValue(username).toString)
}

This uses javax.naming.ldap.Rdn.escapeValue() which is part of the standard Java/Scala library and properly escapes all LDAP special characters including *, (, ), \, and NUL.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions