HIGH ldap injectiondjangobasic auth

Ldap Injection in Django with Basic Auth

Ldap Injection in Django with Basic Auth — how this specific combination creates or exposes the vulnerability

LDAP Injection occurs when an attacker can manipulate LDAP query construction by injecting malicious input. In Django, this risk can surface in custom authentication or group-mapping logic even when you use HTTP Basic Authentication. Basic Auth supplies a username and password via the Authorization header; Django typically validates credentials and then may call into an LDAP backend or a custom directory service to authorize users. If the code builds LDAP filter strings by concatenating user-controlled values without proper escaping, the injected filter can change query semantics, bypass restrictions, or extract sensitive directory data.

Consider a scenario where Django’s authentication pipeline uses a custom LDAP backend after Basic Auth credentials are parsed. A developer might construct an LDAP filter like this:

(&(objectClass=person)(uid={username})(userPassword={password}))

If username comes directly from the Basic Auth credential and is not escaped, an attacker can supply a username such as admin)(uid=*), producing the filter:

(&(objectClass=person)(uid=admin)(uid=*)(userPassword=...))

This injected wildcard can return multiple entries, potentially allowing the attacker to authenticate as another user or enumerate directory contents. Even when Django’s default authentication is not used, custom decorators or permission checks that query LDAP based on Basic Auth identities remain vulnerable if they embed input directly into filters. Common LDAP injection techniques include filter assertion manipulation, distinguished name (DN) injection to alter scope, and leveraging special characters to break filter structure. Because Basic Auth transmits credentials in base64 (not encryption), transport security is essential, but injection is a server-side construction issue. The attack surface is present when LDAP queries embed user-supplied input in filters, search bases, or attribute values without strict validation or escaping.

Another realistic pattern is using the username to dynamically select a base DN for search:

search_base = f"ou=people,dc=example,dc=com,uid={username}"

An attacker can inject additional segments or close the DN prematurely, leading to unauthorized directory access or information disclosure. Because LDAP supports complex escaping rules for special characters like (, ), *, and \, ad-hoc string concatenation is unsafe. Always prefer parameterized APIs or well-tested escaping routines. The combination of Django’s Basic Auth flow and LDAP integration requires rigorous input sanitization to prevent injection, privilege escalation, or data exposure.

Basic Auth-Specific Remediation in Django — concrete code fixes

Secure LDAP usage with Basic Auth in Django starts with avoiding string concatenation for LDAP filters. Use library-provided escaping and parameterized search APIs. Below are concrete, working examples that demonstrate safe patterns.

Safe LDAP filter construction with python-ldap

Use ldap.filter.filter_format to safely embed values into filters:

import ldap
from django.conf import settings

def safe_ldap_auth(username, password):
    # username and password come from Basic Auth credentials
    ldap_uri = settings.LDAP_URI
    conn = ldap.initialize(ldap_uri)
    # Use filter_format to escape special characters in username
    user_filter = ldap.filter.filter_format(
        '(&(objectClass=person)(uid=%s)(userPassword=%s))',
        [username, password]
    )
    try:
        conn.simple_bind_s(username, password)
        # Perform a safe search using parameterized filter
        result = conn.search_s(
            settings.LDAP_BASE_DN,
            ldap.SCOPE_SUBTREE,
            filterstr=user_filter
        )
        return bool(result)
    except ldap.INVALID_CREDENTIALS:
        return False
    finally:
        conn.unbind_s()

Safe dynamic base DN with proper escaping

If you must derive parts of the search base from user input, validate and encode each component:

import re

def safe_search_base(username):
    # Allow only alphanumeric and a few safe characters; reject path separators
    if not re.match(r'^[a-zA-Z0-9._-]+$', username):
        raise ValueError('Invalid username for directory lookup')
    # Use a fixed structure and append the validated component as a single RDN
    return f"uid={username},ou=people,dc=example,dc=com"

# Usage inside a view or permission check
def my_protected_view(request):
    auth = request.META.get('HTTP_AUTHORIZATION')
    if auth and auth.startswith('Basic '):
        import base64
        decoded = base64.b64decode(auth.split(' ')[1]).decode('utf-8')
        username, password = decoded.split(':', 1)
        base = safe_search_base(username)
        # Proceed with ldap.initialize and conn.search_s using base
    # ... rest of view

Django settings and custom authentication backend

Define a custom backend that uses the safe LDAP helper and integrate it into Django’s authentication flow:

# settings.py
AUTHENTICATION_BACKENDS = [
    'myapp.auth.LdapBackend',
    'django.contrib.auth.backends.ModelBackend',
]

# myapp/auth.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model

class LdapBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None):
        if safe_ldap_auth(username, password):
            User = get_user_model()
            user, _ = User.objects.get_or_create(username=username)
            return user
        return None

    def get_user(self, user_id):
        User = get_user_model()
        return User.objects.filter(pk=user_id).first()

Additional hardening

  • Always use TLS (ldaps) to protect credentials in transit, since Basic Auth encodes but does not encrypt.
  • Validate and sanitize any input used in search bases, even when it originates from trusted claims.
  • Apply principle of least privilege for the LDAP bind account used by Django.

These patterns keep user-controlled data out of raw LDAP filter strings and ensure directory queries remain well-formed, reducing the risk of LDAP injection while working with Basic Auth in Django.

Frequently Asked Questions

Can LDAP injection occur if I use Django’s default authentication with Basic Auth?
Django’s default authentication does not use LDAP; injection risk appears only when you add custom LDAP calls. If you extend authentication with a custom backend that builds LDAP filters using user input, injection becomes possible.
Is using environment variables for LDAP credentials sufficient to prevent injection?
Environment variables help protect secrets but do not prevent injection. Injection is about how you build LDAP filters; always escape or parameterize user input regardless of how credentials are stored.