>
>
>
V5620. OWASP. Possible LDAP injection. …


V5620. OWASP. Possible LDAP injection. Potentially tainted data is used in a search filter.

Анализатор обнаружил, что потенциально заражённые данные используются для формирования фильтра LDAP-запроса. Это может стать причиной LDAP-инъекции в случае, если данные будут скомпрометированы. По своей сути данная атака похожа на SQL-инъекции.

Уязвимости типа LDAP-инъекции относятся к категории рисков OWASP Top 10 Application Security Risks 2021: A3:2021-Injection.

Рассмотрим пример:

public void Search()
{
  ....
  string user = textBox.Text;
  string password = pwdBox.Password;

  DirectoryEntry de = new DirectoryEntry();
  DirectorySearcher search = new DirectorySearcher(de);

  search.Filter = $"(&(userId={user})(UserPassword={password}))"; 
  search.PropertiesToLoad.Add("mail");
  search.PropertiesToLoad.Add("telephonenumber");
  SearchResult sresult = search.FindOne();
  if(sresult != null)
  {
    ....
  }
  ....
}

В данном примере формируется фильтр поиска для предоставления некоторых личных данных пользователю, обладающему подходящими логином и паролем. В фильтре используются значения переменных 'user' и 'password', полученные из внешнего источника. Использование данных подобным образом опасно, так как даёт злоумышленнику возможность подделки фильтра поиска.

Для лучшего понимания атаки приведём несколько примеров.

Если в 'user' будет записано "PVS", а в 'password' – "Studio", то получится следующий запрос:

LDAP query: (&(userId=PVS)(UserPassword=Studio))

В этом случае мы получили от пользователя ожидаемые данные и если такая комбинация пользователя и пароля существует, то будет предоставлен доступ.

Но допустим, что в переменных 'user' и 'password' будут записаны следующие значения:

user: PVS)(userId=PVS))(|(userId=PVS)
password: Any

При подстановке этих строк в шаблон получится следующий фильтр:

LDAP query: (&(userId=PVS)(userId=PVS))(|(userId=PVS)(UserPassword=Any))

При использовании такого фильтра поиска доступ будет предоставлен в любом случае, даже если злоумышленник введёт неверный пароль. Это произойдёт из-за того, что LDAP будет обрабатывать только первый фильтр, а (|(userId=PVS)(UserPassword=Any)) просто проигнорирует.

Чтобы защититься от подобной атаки, стоит проводить валидацию всех входных данных или экранировать все специальные символы в данных, которые приходят от пользователей. Существуют методы, которые автоматически экранируют все небезопасные значения.

Пример кода с использованием метода автоматического экранирования из пространства имён 'Microsoft.Security.Application.Encoder':

public void Search()
{
  ....
  string user = textBox.Text;
  string password = pwdBox.Password;

  DirectoryEntry de = new DirectoryEntry();
  DirectorySearcher search = new DirectorySearcher(de);

  user = Encoder.LdapFilterEncode(user);
  password = Encoder.LdapFilterEncode(password);

  search.Filter = $"(&(userId={user})(UserPassword={password}))";    
  search.PropertiesToLoad.Add("mail");
  search.PropertiesToLoad.Add("telephonenumber");
  SearchResult sresult = search.FindOne();
  if (sresult != null)
  {
    ....
  }
  ....
}

Анализатор также считает источниками небезопасных данных параметры методов, доступных из других сборок. Более подробно эта тема раскрыта в заметке "Почему важно проверять значения параметров общедоступных методов".

Рассмотрим пример:

public class LDAPHelper
{
  public void Search(string userName)
  {
    var filter = "(&(objectClass=user)(employeename=" + userName + "))";
    ExecuteQuery(filter);
  }

  private void ExecuteQuery(string filter)
  {
    DirectoryEntry de = new DirectoryEntry();
    DirectorySearcher search = new DirectorySearcher(de);

    search.Filter = filter; 
    search.PropertiesToLoad.Add("mail");
    search.PropertiesToLoad.Add("telephonenumber");
    SearchResult sresult = search.FindOne();
    if (sresult != null)
    {
      ....
    }
  }
}

В данном случае анализатор выдаст предупреждение низкого уровня достоверности при анализе исходного кода метода 'Search' на вызов 'ExecuteQuery'. PVS-Studio отследил передачу небезопасных данных из параметра 'userName' в переменную 'filter' и после в 'ExecuteQuery'.

Защита в таком случае не отличается от приведённой ранее.

public class LDAPHelper
{
  public void Search(string userName)
  {
    userName = Encoder.LdapFilterEncode(userName);
    var filter = "(&(objectClass=user)(employeename=" + userName + "))";
    ExecuteQuery(filter); 
  }

  private void ExecuteQuery(string filter)
  {
    DirectoryEntry de = new DirectoryEntry();
    DirectorySearcher search = new DirectorySearcher(de);

    search.Filter = filter;
    ....
  }
}

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки непроверенного использования чувствительных данных (ввода пользователя, файлов, сети и пр.).

Данная диагностика классифицируется как: