LDAP injection - Go

LDAP injection - Go

Need

Prevention of LDAP injection attacks

Context

  • Usage of Go 1.16 for developing high-performance and efficient applications
  • Usage of gin-gonic/gin for building web applications in Go
  • Usage of gopkg.in/ldap.v2 for LDAP (Lightweight Directory Access Protocol) integration
  • Usage of log for logging messages and debugging purposes

Description

Non compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"gopkg.in/ldap.v2"
	"log"
)

func main() {
	r := gin.Default()

	r.GET("/search", func(c *gin.Context) {
		username := c.Query("username")

		l, err := ldap.Dial("tcp", "localhost:389")
		if err != nil {
			log.Fatal(err)
		}
		defer l.Close()

		searchRequest := ldap.NewSearchRequest(
			"dc=example,dc=com",
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			"(&(objectClass=organizationalPerson)(uid=" + username + "))",
			[]string{"dn", "cn", "givenName", "sn"},
			nil,
		)

		sr, err := l.Search(searchRequest)
		if err != nil {
			log.Fatal(err)
		}

		for _, entry := range sr.Entries {
			log.Println(entry.DN)
		}
	})

	r.Run()
}
        
        

The above code is an example of an LDAP injection vulnerability in a Go application using gin framework. The application is set up to search for a user in an LDAP directory using a username passed as a query parameter.

The vulnerability lies in the following line of code:

                "(&(objectClass=organizationalPerson)(uid=" + username + "))"

            


Here, the application directly concatenates the username from the query parameters into the LDAP query without any sanitization or encoding. This means that if the username parameter contains any special characters used in LDAP queries, it could modify the query to return unintended results or perform unauthorized actions.

For example, an attacker could pass a username like admin*)(uid=*))(|(uid=* which would modify the query to return all users instead of just the admin user. This is known as an LDAP injection attack.

This vulnerability can lead to serious security issues such as unauthorized access, data leaks, and denial of service. To prevent this, it is important to always sanitize and validate untrusted data before using it in an LDAP query.

Steps

  • Validate and sanitize user input: Before using the 'username' parameter in the LDAP query, validate and sanitize the input to ensure it only contains the expected characters.
  • Use parameterized queries: Instead of concatenating the 'username' parameter directly into the LDAP query string, use parameterized queries or prepared statements to separate the query logic from the user input.
  • Implement input validation: Implement input validation to ensure that the 'username' parameter follows the expected format and does not contain any malicious characters or LDAP metacharacters.
  • Use LDAP escape characters: If it is necessary to include user input directly in the LDAP query, make sure to properly escape any special characters using LDAP escape sequences.
  • Limit privileges: Ensure that the LDAP user account used by the application has limited privileges and only has access to the necessary LDAP resources.
  • Implement secure coding practices: Follow secure coding practices to minimize the risk of injection vulnerabilities, such as input validation, output encoding, and parameterized queries.
  • Regularly update dependencies: Keep the dependencies, such as the 'ldap' package, up to date to benefit from any security patches or fixes.
  • Perform security testing: Regularly perform security testing, including penetration testing and code reviews, to identify and address any potential vulnerabilities in the application.

Compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"gopkg.in/ldap.v2"
	"log"
	"net/url"
)

func main() {
	r := gin.Default()

	r.GET("/search", func(c *gin.Context) {
		username := c.Query("username")

		// Validate and sanitize user input
		username = url.QueryEscape(username)

		l, err := ldap.Dial("tcp", "localhost:389")
		if err != nil {
			log.Fatal(err)
		}
		defer l.Close()

		// Use parameterized queries
		searchRequest := ldap.NewSearchRequest(
			"dc=example,dc=com",
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			"(&(objectClass=organizationalPerson)(uid=" + ldap.EscapeFilter(username) + "))",
			[]string{"dn", "cn", "givenName", "sn"},
			nil,
		)

		sr, err := l.Search(searchRequest)
		if err != nil {
			log.Fatal(err)
		}

		for _, entry := range sr.Entries {
			log.Println(entry.DN)
		}
	})

	r.Run()
}
        
        

The updated code includes several changes to mitigate the risk of LDAP injection:

1. Validate and sanitize user input: The 'username' parameter is sanitized using the url.QueryEscape function. This function encodes special characters in the 'username' parameter, which helps prevent LDAP injection attacks.

2. Use parameterized queries: The 'username' parameter is no longer concatenated directly into the LDAP query string. Instead, the ldap.EscapeFilter function is used to escape any special characters in the 'username' parameter. This function replaces special characters with their escaped equivalents, which prevents these characters from modifying the LDAP query.

3. Limit privileges and implement secure coding practices: Although not shown in the code, it's recommended to ensure that the LDAP user account used by the application has limited privileges and only has access to the necessary LDAP resources. Additionally, following secure coding practices such as input validation, output encoding, and parameterized queries can help minimize the risk of injection vulnerabilities.

4. Regularly update dependencies and perform security testing: Keeping the dependencies, such as the 'ldap' package, up to date can help benefit from any security patches or fixes. Regularly performing security testing, including penetration testing and code reviews, can help identify and address any potential vulnerabilities in the application.

References