Анализатор обнаружил, что потенциально заражённые данные используются для формирования фильтра 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; .... } }
Данная диагностика классифицируется как:
|