Skip to main content

LDAP injection

Need

Prevention of LDAP injection attacks

Context

  • Usage of Scala for building scalable and high-performance applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of javax.naming.directory for accessing and manipulating directory services in Java
  • Usage of javax.naming for accessing and manipulating naming and directory services in Java

Description

Non compliant code

import play.api.mvc._
import javax.naming.directory._
import javax.naming._

class UserController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def getUser(username: String, password: String) = Action {
val env = new java.util.Hashtable[String, String]()
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com")

val ctx = new InitialDirContext(env)
val searchControls = new SearchControls()
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE)

val searchFilter = s"(uid=$username)(userPassword=$password)"
val searchBase = "dc=example,dc=com"

val results = ctx.search(searchBase, searchFilter, searchControls)
if (results.hasMoreElements) {
Ok("User found")
} else {
NotFound("User not found")
}
}
}

In the above Scala code, a Play Framework controller is defined to handle user authentication. The method getUser accepts username and password as parameters which are directly incorporated into the LDAP search filter without any sanitization or encoding.

This is a classic example of an LDAP Injection vulnerability. An attacker can provide specially crafted input to manipulate the LDAP query. For example, if an attacker sets the username parameter as *)(uid=*))(|(uid=*, this would return all users in the LDAP directory, bypassing the password check.

The root of this vulnerability lies in the line:

val searchFilter = s"(uid=$username)(userPassword=$password)"

Here, the username and password are directly inserted into the LDAP query without any form of validation, encoding or escaping. This allows for LDAP Injection attacks where the attacker can manipulate the LDAP query to perform unauthorized actions.

Steps

  • Validate and sanitize user input: Ensure that the username and password inputs are properly validated and sanitized to prevent any malicious LDAP injection.
  • Use parameterized queries: Instead of directly concatenating user input into the LDAP query, use parameterized queries or prepared statements to safely include user input in the query.
  • Escape special characters: If you need to include user input directly in the LDAP query, make sure to properly escape any special characters to prevent injection attacks.
  • Implement input validation: Implement input validation to ensure that the username and password inputs only contain valid characters and meet the required format.
  • Limit privileges: Ensure that the LDAP user used by the application has limited privileges and can only perform the necessary operations.
  • Implement secure connection: Use a secure connection (LDAPS) when connecting to the LDAP server to protect the data transmitted between the application and the server.
  • Implement access controls: Implement access controls on the LDAP server to restrict the operations that can be performed by the application.
  • Regularly update libraries and frameworks: Keep the Play framework and any LDAP-related libraries up to date to benefit from security patches and fixes.
  • Perform security testing: Regularly perform security testing, including penetration testing, to identify and address any vulnerabilities in the application.

Compliant code

import play.api.mvc._
import javax.naming.directory._
import javax.naming._
import org.apache.directory.api.ldap.model.name.Dn
import org.apache.directory.api.ldap.model.entry.DefaultEntry
import org.apache.directory.api.ldap.model.message.SearchScope

class UserController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def getUser(username: String, password: String) = Action {
val env = new java.util.Hashtable[String, String]()
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com")

val ctx = new InitialDirContext(env)
val searchControls = new SearchControls()
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE)

// Validate and sanitize user input
val sanitizedUsername = Dn.escapeValue(username)
val sanitizedPassword = Dn.escapeValue(password)

// Use parameterized queries
val searchFilter = s"(uid=${sanitizedUsername})(userPassword=${sanitizedPassword})"
val searchBase = "dc=example,dc=com"

val results = ctx.search(searchBase, searchFilter, searchControls)
if (results.hasMoreElements) {
Ok("User found")
} else {
NotFound("User not found")
}
}
}

The updated code includes the following changes to fix the LDAP injection vulnerability:

  1. Validate and sanitize user input: The Dn.escapeValue method from the Apache Directory LDAP API is used to sanitize the username and password inputs. This method escapes any special characters in the input that could modify the LDAP query.

  2. Use parameterized queries: The sanitized username and password inputs are included in the LDAP query using string interpolation, which is safe from LDAP injection attacks. The inputs are enclosed in curly braces ({}) to separate them from the query text.

These changes ensure that the LDAP query is built using safe, sanitized user input, preventing LDAP injection attacks. The application should also implement the other security measures mentioned in the steps to fix the vulnerability, such as using a secure connection (LDAPS), limiting the privileges of the LDAP user, implementing access controls on the LDAP server, keeping libraries and frameworks up to date, and regularly performing security testing.

References