HIGH ldap injectionfiber

Ldap Injection in Fiber

How Ldap Injection Manifests in Fiber

LDAP injection in Fiber applications typically occurs when user-supplied input is concatenated directly into LDAP filter strings without proper sanitization. Fiber's LDAP integration patterns often involve constructing queries using fmt.Sprintf or string concatenation, creating injection opportunities.

Consider this common Fiber pattern:

func getUser(c *fiber.Ctx) error {
    username := c.Query("username")
    filter := fmt.Sprintf("(uid=%s)", username)
    
    // LDAP query construction
    searchRequest := ldap.NewSearchRequest(
        "dc=example,dc=com",
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        filter, 
        []string{"uid", "cn", "mail"},
        nil,
    )
    
    sr, err := connection.Search(searchRequest)
    if err != nil {
        return c.Status(500).SendString("LDAP search failed")
    }
    
    return c.JSON(sr.Entries)
}

The vulnerability becomes apparent when examining how LDAP filter syntax can be manipulated. An attacker can inject characters like *, (, ), and | to modify query logic:

// Malicious input: admin*)(|(uid=*

This transforms the filter into:

(uid=admin*)(|(uid=*

Which effectively becomes a logical OR, potentially returning all user entries or bypassing authentication entirely.

Another Fiber-specific pattern involves using LDAP for authentication:

func login(c *fiber.Ctx) error {
    username := c.FormValue("username")
    password := c.FormValue("password")
    
    filter := fmt.Sprintf("(uid=%s)", username)
    
    searchRequest := ldap.NewSearchRequest(
        "dc=example,dc=com",
        ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
        filter,
        []string{"dn", "uid"},
        nil,
    )
    
    sr, err := connection.Search(searchRequest)
    if err != nil || len(sr.Entries) != 1 {
        return c.Status(401).SendString("Invalid credentials")
    }
    
    userDN := sr.Entries[0].DN
    
    // Bind with user credentials
    err = connection.Bind(userDN, password)
    if err != nil {
        return c.Status(401).SendString("Invalid credentials")
    }
    
    return c.SendString("Login successful")
}

Even though the bind operation uses the extracted DN, the initial search remains vulnerable. An attacker could potentially enumerate valid usernames or cause denial of service through malformed queries.

Fiber-Specific Detection

Detecting LDAP injection in Fiber applications requires examining both code patterns and runtime behavior. Static analysis can identify vulnerable code constructs:

package main

import (
    "go/ast"
    "go/parser"
    "go/token"
    "log"
    "path/filepath"
)

func findLdapInjectionVulnerabilities(rootDir string) []string {
    var vulnerableFiles []string
    
    filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
        if err != nil || !strings.HasSuffix(path, ".go") {
            return nil
        }
        
        fset := token.NewFileSet()
        f, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
        if err != nil {
            return nil
        }
        
        ast.Inspect(f, func(n ast.Node) bool {
            switch x := n.(type) {
            case *ast.CallExpr:
                // Look for ldap.NewSearchRequest calls
                if isLdapSearchCall(x) {
                    // Check if filter argument contains string concatenation
                    if hasUnsafeStringConcat(x.Args[6]) {
                        vulnerableFiles = append(vulnerableFiles, path)
                    }
                }
            }
            return true
        })
        
        return nil
    })
    
    return vulnerableFiles
}

func isLdapSearchCall(expr *ast.CallExpr) bool {
    if sel, ok := expr.Fun.(*ast.SelectorExpr); ok {
        if x, ok := sel.X.(*ast.Ident); ok && x.Name == "ldap" && sel.Sel.Name == "NewSearchRequest" {
            return true
        }
    }
    return false
}

func hasUnsafeStringConcat(arg ast.Expr) bool {
    switch a := arg.(type) {
    case *ast.CallExpr:
        // Check for fmt.Sprintf, strings.Join, etc.
        if isUnsafeFormattingCall(a) {
            return true
        }
    case *ast.BinaryExpr:
        // Check for string concatenation
        if a.Op == token.ADD && isStringLiteral(a.X) || isStringLiteral(a.Y) {
            return true
        }
    }
    return false
}

Runtime detection with middleBrick provides another layer of security validation. The scanner identifies LDAP injection vulnerabilities by:

  • Analyzing parameter handling patterns in API endpoints
  • Checking for LDAP-specific query construction methods
  • Testing for LDAP filter syntax injection possibilities
  • Examining authentication flows that use LDAP

middleBrick's LDAP injection detection specifically looks for:

// Testing for LDAP filter injection
func testLdapInjection(endpoint string) (bool, string) {
    testCases := []string{
        "*",
        "*user",
        "admin*)(uid=*",
        "*)(uid=admin",
        "*)(&)",
    }
    
    for _, test := range testCases {
        response := makeRequest(endpoint, test)
        if isUnexpectedSuccess(response) {
            return true, fmt.Sprintf("LDAP injection possible with: %s", test)
        }
    }
    
    return false, "No LDAP injection detected"
}

The scanner also checks for proper error handling, as LDAP injection attempts often reveal information through error messages that expose directory structure or query syntax.

Fiber-Specific Remediation

Remediating LDAP injection in Fiber requires adopting safe query construction practices. The primary defense is using parameterized LDAP filters instead of string concatenation.

Using ldap3's filter construction utilities:

import "github.com/go-ldap/ldap/v3"

func getUserSafe(c *fiber.Ctx) error {
    username := c.Query("username")
    
    // Use proper filter escaping
    escapedUsername := ldap.EscapeFilter(username)
    filter := fmt.Sprintf("(uid=%s)", escapedUsername)
    
    searchRequest := ldap.NewSearchRequest(
        "dc=example,dc=com",
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        filter,
        []string{"uid", "cn", "mail"},
        nil,
    )
    
    sr, err := connection.Search(searchRequest)
    if err != nil {
        return c.Status(500).SendString("LDAP search failed")
    }
    
    return c.JSON(sr.Entries)
}

Better approach using filter builders:

import "github.com/go-ldap/ldap/v3"

func getUserSafeBuilder(c *fiber.Ctx) error {
    username := c.Query("username")
    
    // Construct filter using builder pattern
    filter := ldap.NewFilter()
    filter.AddCondition("uid", ldap.FilterEquality, username)
    
    searchRequest := ldap.NewSearchRequest(
        "dc=example,dc=com",
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        filter.String(),
        []string{"uid", "cn", "mail"},
        nil,
    )
    
    sr, err := connection.Search(searchRequest)
    if err != nil {
        return c.Status(500).SendString("LDAP search failed")
    }
    
    return c.JSON(sr.Entries)
}

For authentication scenarios, implement proper input validation and use context-aware LDAP operations:

func loginSecure(c *fiber.Ctx) error {
    username := c.FormValue("username")
    password := c.FormValue("password")
    
    // Validate input length and allowed characters
    if len(username) > 255 || len(password) > 255 {
        return c.Status(400).SendString("Input too long")
    }
    
    if !isValidUsername(username) {
        return c.Status(400).SendString("Invalid username format")
    }
    
    // Use search with proper escaping
    escapedUsername := ldap.EscapeFilter(username)
    filter := fmt.Sprintf("(uid=%s)", escapedUsername)
    
    searchRequest := ldap.NewSearchRequest(
        "dc=example,dc=com",
        ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
        filter,
        []string{"dn"},
        nil,
    )
    
    sr, err := connection.Search(searchRequest)
    if err != nil || len(sr.Entries) != 1 {
        return c.Status(401).SendString("Invalid credentials")
    }
    
    userDN := sr.Entries[0].DN
    
    // Use a dedicated bind context with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    err = connection.Bind(userDN, password)
    if err != nil {
        return c.Status(401).SendString("Invalid credentials")
    }
    
    return c.SendString("Login successful")
}

func isValidUsername(username string) bool {
    // Allow only alphanumeric, dot, underscore, hyphen
    matched, _ := regexp.MatchString(`^[a-zA-Z0-9._-]+$`, username)
    return matched
}

Implementing rate limiting for LDAP operations prevents brute force attacks:

var ldapLimiter = middleware.NewRateLimit(10, time.Minute, "ldap:")

func loginWithRateLimit(c *fiber.Ctx) error {
    if !ldapLimiter.Allow(c.IP()) {
        return c.Status(429).SendString("Too many LDAP attempts")
    }
    
    return loginSecure(c)
}

Frequently Asked Questions

Why is LDAP injection particularly dangerous in Fiber applications?
LDAP injection in Fiber is dangerous because it can bypass authentication, expose directory structure, and potentially lead to full system compromise. Fiber's common patterns of using fmt.Sprintf for query construction make it easy to accidentally introduce vulnerabilities. The injection can allow attackers to enumerate valid usernames, bypass access controls, or cause denial of service through malformed queries.
How does middleBrick detect LDAP injection vulnerabilities in Fiber APIs?
middleBrick detects LDAP injection by analyzing API endpoints for LDAP query construction patterns, testing for LDAP filter syntax injection possibilities, and examining authentication flows. The scanner looks for unsafe string concatenation in filter construction, tests with LDAP-specific injection payloads, and checks for proper error handling that might leak directory information. It also validates that input is properly sanitized before being used in LDAP operations.