Анализатор обнаружил создание команды уровня операционной системы из непроверенных данных, полученных из внешнего источника. Это может стать причиной возникновения уязвимости command injection.
В OWASP Top 10 Application Security Risks инъекции команд относятся к следующим категориям:
Рассмотрим пример:
HttpRequest _request; string _pathToExecutor; private void ExecuteOperation() { .... String operationNumber = _request.Form["operationNumber"]; Process.Start("cmd", $"/c {_pathToExecutor} {operationNumber}"); .... }
В представленном коде из запроса считывается номер операции, которую должен выполнить вызываемый процесс. Таким образом, набор операций чётко ограничен. Тем не менее, злоумышленник может передать в качестве значения параметра "operationNumber" специальную строку, которая позволит выполнить дополнительные несанкционированные действия. К примеру, в "operationNumber" может храниться следующая строка:
0 & del /q /f /s *.*
Допустим, что в '_pathToExecutor' записан путь 'executor.exe'. Тогда в результате вызова 'Process.Start' будет выполнена следующая команда:
cmd /c executor.exe 0 & del /q /f /s *.*
Символ '&' здесь будет интерпретирован как разделитель команд. Инструкция 'del' с такими аргументами приведёт к удалению всех файлов в текущей и вложенных папках (за исключением тех, для которых у приложения нет достаточных прав доступа). Таким образом, правильно подобранное значение в параметре "operationNumber" позволяет совершить вредоносные действия.
Во избежание появления уязвимости рекомендуется всегда проверять входные данные. Конкретный способ проверки зависит от ситуации. В указанном выше примере будет достаточно убедиться, что в переменной 'operationNumber' записано число:
private void ExecuteOperation() { String operationNumber = _request.Form["operationNumber"]; if (uint.TryParse(operationNumber, out uint number)) Process.Start("cmd", $"/c {_pathToExecutor} {number}"); }
Параметры методов, доступных из других сборок, также являются источниками небезопасных данных, хотя предупреждение для таких источников будет выдано с низким уровнем достоверности. Подробное объяснение данной позиции приведено в заметке "Почему важно проверять значения параметров общедоступных методов".
В качестве примера рассмотрим следующий код:
private string _userCreatorPath; public void CreateUser(string userName, bool createAdmin) { string args = $"--name {userName}"; if (createAdmin) args += " --roles ADMIN"; RunCreatorProcess(args); // <= } private void RunCreatorProcess(string arguments) { Process.Start(_userCreatorPath, arguments).WaitForExit(); }
В данном коде пользователь создаётся с помощью процесса, запускаемого методом 'RunCreatorProcess'. Этот пользователь должен получить права администратора только в том случае, если флаг 'createAdmin' имеет значение 'true'.
Код из библиотеки, зависимой от текущей, может вызывать метод 'CreateUser' для создания пользователя. В параметр 'userName' может быть передано, к примеру, значение какого-нибудь параметра запроса. При этом высока вероятность того, что никаких проверок в вызывающем коде не производится, так как разработчик будет рассчитывать на их наличие в методе 'CreateUser'. Таким образом, и в библиотеке, и в использующем её коде отсутствует валидация 'userName'.
В результате правильно подобранное имя позволит злоумышленнику создать пользователя с правами администратора вне зависимости от значения флага 'createAdmin', который, очевидно, будет равен 'false' в большинстве случаев. Допустим, что в параметр 'userName' записана следующая строка:
superHacker --roles ADMIN
После подстановки строка аргументов будет выглядеть так же, как если бы в 'createAdmin' было записано значение 'true':
--name superHacker --roles ADMIN
Таким образом, даже не имея прав на создание администратора, злоумышленник сможет создать пользователя с правами администратора и использовать его далее в своих целях.
В данном случае для защиты нужно проверять имя пользователя на отсутствие запрещённых символов. Например, можно разрешить использование только латинских букв и цифр:
public void CreateUser(string userName, bool createAdmin) { if (!Regex.IsMatch(userName, @"^[a-zA-Z0-9]+$")) { // error handling return; } string args = $"--name {userName}"; if (createAdmin) args += " --roles ADMIN"; RunCreatorProcess(args); }
Данная диагностика классифицируется как: