Prevention of LDAP injection attacks
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)"
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.
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.