Anonymous LDAP binding

Anonymous LDAP binding

Description

An anonymous LDAP bind does not establish the client’s identity. The server’s anonymous-access permissions determine which directory data can actually be read or changed. Allowing anonymous access to sensitive data or modification operations may expose information or permit unauthorized changes.

Potential impact

  • Sensitive data exposure: Anonymous clients may read user information or group lists when the server permits it.
  • Unauthorized changes: Anonymous write permissions may allow data manipulation and, depending on the affected data, privilege escalation.
  • Service abuse: Large volumes of anonymous requests may contribute to denial of service.

Remediation

  1. Explicitly configure a server-supported authentication mechanism for protected connections. Use verified TLS with "simple" authentication; do not assume that the string "strong" alone configures secure SASL.
  2. Use a least-privileged account and obtain its password from a secret store or a restricted execution environment. Reject connections when the password is missing or empty.
  3. Deny anonymous reads and writes to sensitive data on the server. Changing client authentication settings does not change the server’s anonymous permissions.

Examples

Before

java
import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;

public class UnsafeLDAP {
    public static void main(String[] args) throws Exception {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://ldap.example.org:389");
        env.put(Context.SECURITY_AUTHENTICATION, "none"); // Request an anonymous bind.

        InitialDirContext ldapContext = new InitialDirContext(env);
    }
}

After

This requires an LDAPS server and a trust store that allow certificate and hostname verification. app-reader is an example account with only the required read permissions; replace it with the actual account DN.

java
import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;

public class SecureLDAP {
    public static void main(String[] args) throws Exception {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldaps://ldap.example.org:636");
        env.put(Context.SECURITY_AUTHENTICATION, "simple"); // Authenticate with a password over TLS.
        env.put(Context.SECURITY_PRINCIPAL, "cn=app-reader,dc=example,dc=org");
        env.put(Context.SECURITY_CREDENTIALS, getLdapPassword()); // Load the password from the runtime environment.

        InitialDirContext ldapContext = new InitialDirContext(env);
        ldapContext.close();
    }

    private static String getLdapPassword() {
        // Validate the password supplied by the restricted execution environment.
        String password = System.getenv("LDAP_PASSWORD");
        if (password == null || password.isEmpty()) {
            throw new IllegalStateException("LDAP password is not set");
        }
        return password;
    }
}

Explanation:

  • Before: Requests an anonymous bind with "none". Access remains limited by the server’s permissions for anonymous clients.
  • After: Authenticates over TLS using a nonempty, externally supplied password. Environment variables are not themselves a secret store, so protect execution-environment access and logs as well.

References