Skip to content

LDAP Injection

What does this mean ?

LDAP Injection is a type of attack that targets web-based applications that generate LDAP statements depending on user input. When an application fails to correctly sanitize user input, LDAP statements can be modified via a local proxy.

What can happen ?

This might lead to the execution of arbitrary actions, such as giving rights to illegal searches and modifying items inside the LDAP tree. The same complex exploitation techniques that are accessible in SQL Injection may also be used in LDAP Injection.

Recommendation ?

  • Using the appropriate LDAP encoding function, escape all variables.
  • Every user input that may be utilized in LDAP queries should be sanitized according to application standards and encoded to guarantee that any leftover LDAP special characters, including at least ()! | & *, are properly escaped.
  • For optimal security and ease, a suitable framework or library for escaping special characters and constructing LDAP filters should be utilized.

Sample Code

Vulnerable :

using System.DirectoryServices;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2078LDAPInjectionNoncompliantController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public DirectorySearcher ds { get; set; }

        public IActionResult Authenticate(string user, string pass)
        {
            ds.Filter = "(&(uid=" + user + ")(userPassword=" + pass + "))"; // Noncompliant

            // If the special value "*)(uid=*))(|(uid=*" is passed as user, authentication is bypassed
            // Indeed, if it is passed as a user, the filter becomes:
            // (&(uid=*)(uid=*))(|(uid=*)(userPassword=...))
            // as uid=* match all users, it is equivalent to:
            // (|(uid=*)(userPassword=...))
            // again, as uid=* match all users, the filter becomes useless

            return Content(ds.FindOne() != null ? "success" : "fail");
        }
    }
}

Non Vulnerable :

using System.DirectoryServices;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2078LDAPInjectionCompliantController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public DirectorySearcher ds { get; set; }

        public IActionResult Authenticate(string user, string pass)
        {
            // restrict the username and password to letters only
            if (!Regex.IsMatch(user, "^[a-zA-Z]+$") || !Regex.IsMatch(pass, "^[a-zA-Z]+$"))
            {
                return BadRequest();
            }

            ds.Filter = "(&(uid=" + user + ")(userPassword=" + pass + "))"; // Now safe
            return Content(ds.FindOne() != null ? "success" : "fail");
        }
    }
}

Vulnerable :

public boolean authenticate(javax.servlet.http.HttpServletRequest request, DirContext ctx) throws NamingException {
  String user = request.getParameter("user");
  String pass = request.getParameter("pass");

  String filter = "(&(uid=" + user + ")(userPassword=" + pass + "))"; // Unsafe

  // If the special value "*)(uid=*))(|(uid=*" is passed as user, authentication is bypassed
  // Indeed, if it is passed as a user, the filter becomes:
  // (&(uid=*)(uid=*))(|(uid=*)(userPassword=...))
  // as uid=* match all users, it is equivalent to:
  // (|(uid=*)(userPassword=...))
  // again, as uid=* match all users, the filter becomes useless

  NamingEnumeration<SearchResult> results = ctx.search("ou=system", filter, new SearchControls()); // Noncompliant
  return results.hasMore();
}

Non Vulnerable :

public boolean authenticate(javax.servlet.http.HttpServletRequest request, DirContext ctx) throws NamingException {
  String user = request.getParameter("user");
  String pass = request.getParameter("pass");

  String filter = "(&(uid={0})(userPassword={1}))"; // Safe

  NamingEnumeration<SearchResult> results = ctx.search("ou=system", filter, new String[]{user, pass}, new SearchControls());
  return results.hasMore();
}

Vulnerable :

$user = $_GET["user"];
$pass = $_GET["pass"];

$filter = "(&(uid=" . $user . ")(userPassword=" . $pass . "))"; // Unsafe

$ds = ...
$basedn = "o=My Company, c=US";

$sr = ldap_list($ds, $basedn, $filter); // Noncompliant

Non Vulnerable :

function sanitize_ldap_criteria($val) {
  $val = str_replace(['\\', '*', '(', ')'], ['\5c', '\2a', '\28', '\29'], $val);
  for ($i = 0; $i<strlen($val); $i++) {
    $char = substr($val, $i, 1);
    if (ord($char)<32) {
      $hex = dechex(ord($char));
      if (strlen($hex) == 1) $hex = '0' . $hex;
      $val = str_replace($char, '\\' . $hex, $val);
    }
  }
  return $val;
}

$user = sanitize_ldap_criteria( $_GET["user"] );
$pass = sanitize_ldap_criteria( $_GET["pass"] );

$filter = "(&(uid=" . $user . ")(userPassword=" . $pass . "))"; // Safe

$ds = ...
$basedn = "o=My Company, c=US";

$sr = ldap_list($ds, $basedn, $filter);

References