>
>
>
V5616. OWASP. Possible command injectio…


V5616. OWASP. Possible command injection. Potentially tainted data is used to create OS command.

Анализатор обнаружил создание команды уровня операционной системы из непроверенных данных, полученных из внешнего источника. Это может стать причиной возникновения уязвимости 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);
}

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