Ldap Injection in Chi with Basic Auth
Ldap Injection in Chi with Basic Auth — how this specific combination creates or exposes the vulnerability
LDAP Injection is a server-side injection risk that occurs when an application builds LDAP query strings using unsanitized user input. When combined with HTTP Basic Authentication in the Chi routing library for Go, the typical pattern of extracting credentials from the Authorization header and binding to an LDAP server can become unsafe if the extracted username or password fragments are later used to construct LDAP search filters or DNs without validation or escaping.
Chi provides a convenient way to read Basic Auth credentials via BasicAuth middleware or a custom handler that calls r.BasicAuth(). If the returned username is directly interpolated into an LDAP filter, such as (&(uid=%s)(password=%s)), characters like *, (, ), and \00 can change the semantics of the LDAP query, enabling authentication bypass or unintended data retrieval. Even when the goal is only to search for a user before binding, unsanitized input can lead to LDAP injection that discloses other user entries or elevates privileges.
In a typical Chi service using Basic Auth, the flow is:
- Client sends an
Authorization: Basic base64(username:password)header. - Chi decodes the credentials and passes them to application code.
- The application uses those credentials to perform an LDAP search or direct bind.
If the username is used in an LDAP filter without escaping, an attacker can supply a payload like admin)(uid=* to manipulate the filter structure. This can unintentionally match additional entries or bypass intended access controls. Although Basic Auth itself transmits credentials in base64 (not encryption), the injection risk arises not from the transport but from how the application uses the extracted values in LDAP operations. The LDAP server processes the modified filter as intended, potentially returning sensitive entries or allowing authentication as a different identity.
middleBrick scans unauthenticated attack surfaces and includes checks for authentication mechanisms and input validation. In the context of LDAP interactions, findings may highlight improper input handling where credentials derived from Basic Auth are used in query construction. These findings map to relevant parts of the OWASP API Top 10 and common server-side injection patterns, emphasizing the need to treat extracted credentials as untrusted data.
Basic Auth-Specific Remediation in Chi — concrete code fixes
To prevent LDAP injection when using Basic Auth in Chi, always treat extracted credentials as untrusted. Do not concatenate user-controlled values into LDAP filters or DNs. Instead, use parameterized LDAP APIs or properly escape special characters according to the LDAP filter escape rules defined in RFC 4515.
Below are two concrete, working examples in Go using Chi. The first demonstrates the vulnerable pattern to avoid, and the second shows the safe approach with escaping and parameterized binding.
Vulnerable pattern (do not use)
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-ldap/ldap/v3"
)
func unsafeHandler(ldapConn *ldap.Conn) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// UNSAFE: directly interpolating user input into filter
filter := "(&(uid=" + user + ")(password=" + pass + "))"
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"},
nil,
)
// Risk: LDAP injection via user or pass
result, err := ldapConn.Search(searchRequest)
if err != nil || len(result.Entries) == 0 {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Potentially unsafe bind using raw credentials
bindErr := ldapConn.Bind(user, pass)
if bindErr != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
w.Write([]byte("Authenticated"))
}
}
Secure remediation with escaping and parameterized usage
package main
import (
"net/http"
"github.com/go-chi/chi/ctxmiddleware"
"github.com/go-chi/chi/v5"
"github.com/go-ldap/ldap/v3"
)
// escapeLDAPFilter escapes special characters per RFC 4519
func escapeLDAPFilter(s string) string {
// Replace according to RFC 4519: * -> \2a, ( -> \28, ) -> \29, \ -> \5c, \00 -> \00
// For brevity, using a library or a full implementation is recommended.
// Placeholder: in production, use a well-tested escape function.
return ldap.EscapeFilter(s)
}
func safeHandler(ldapConn *ldap.Conn) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// SAFE: escape user input before using in filter
safeUser := escapeLDAPFilter(user)
safePass := escapeLDAPFilter(pass)
filter := "(&(uid=" + safeUser + ")(password=" + safePass + "))"
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"},
nil,
)
result, err := ldapConn.Search(searchRequest)
if err != nil || len(result.Entries) == 0 {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Prefer safe bind using a known DN pattern instead of raw user DN
bindDN := "uid=" + safeUser + ",ou=people,dc=example,dc=com"
bindErr := ldapConn.Bind(bindDN, pass)
if bindErr != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
w.Write([]byte("Authenticated"))
}
}
Additional recommendations include using the ldap package’s built-in escaping utilities, avoiding constructing DNs from raw input, and preferring bind DN templates that reference known directory structure rather than raw user-provided identifiers. middleBrick’s authentication and input validation checks can help surface places where credentials-derived values are used in LDAP operations without proper sanitization.