Для анализа была выбрана уязвимость CVE-2023–48702. Она связана с open-source проектом Jellyfin, в котором была выявлена возможность удаленного выполнения кода (RCE) из-за отсутствия проверки загружаемого файла.
Уязвимость скрывалась в эндпоинте /System/MediaEncoder/Path. Его обработка начинается в файле ConfigurationController.cs путем вызова метода UpdateEncoderPath:
public ActionResult UpdateMediaEncoderPath([FromBody, Required] MediaEncoderPathDto mediaEncoderPath)
{
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
return NoContent();
}Метод UpdateEncoderPath файла MediaEncoder.cs обрабатывает запрос администратора на смену пути к FFmpeg (внешний сервис для обработки мультимедиа). Если переданный path — это существующая папка, функция пытается найти внутри неё исполняемый файл с именем ffmpeg (или ffmpeg.exe). Проверка выполняется путем запуска исполняемого файла (вызов ValidateVersion файла EncoderValidator.cs):
public void UpdateEncoderPath(string path, string pathType)
{
…
if (!new EncoderValidator(_logger, newPath).ValidateVersion())
{
throw new ResourceNotFoundException();
}
}
…
}Метод ValidateVersion в свою очередь запускает функцию GetProcessOutput, которая уже непосредственно запускает процесс (ProcessStartInfo):
public bool ValidateVersion()
{
string output;
try
{
output = GetProcessOutput(_encoderPath, "-version", false, null);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating encoder");
return false;
}
if (string.IsNullOrWhiteSpace(output))
{
_logger.LogError("FFmpeg validation: The process returned no result");
return false;
}
_logger.LogDebug("ffmpeg output: {Output}", output);
return ValidateVersionInternal(output);
}
…
private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
{
using (var process = new Process()
{
StartInfo = new ProcessStartInfo(path, arguments)
{
CreateNoWindow = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
RedirectStandardInput = !string.IsNullOrEmpty(testKey),
RedirectStandardOutput = true,
RedirectStandardError = true
}
})
{
_logger.LogDebug("Running {Path} {Arguments}", path, arguments);
process.Start();
if (!string.IsNullOrEmpty(testKey))
{
process.StandardInput.Write(testKey);
}
return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
}
Для воспроизведения уязвимости необходимо выполнить следующие шаги:
1. Войти под учетной записью администратора
2. Перейти в раздел Dashboard->Playback
3. Ввести в поле FFmpeg path путь к исполняемому файлу с эксплойтом (для демонстрации была выбрана программа калькулятора)
4. Сохранить изменения
Запуск CodeQL с поиском описанных ранее CWE выявил уязвимость Command Line Injection:
Также в рамках задания было реализовано собственное правило для выявления уязвимости. CodeQL правила пишутся на языке QL. Для поиска уязвимости был использован Taint tracking (анализ заражения) — техника отслеживания пути "опасных" данных через программу.
Программа не выявила уязвимость Command Line Injection, только потенциальные проблемы, связанные с Path Traversal:
Выполнить taint-анализ для выявления уязвимости оказалось нельзя, так как поток данных в Jellyfin проходит через несколько файлов, а межфайловый анализ (interfile: true) доступен только в платной версии.
Правило основано на структурном сопоставлении с шаблоном (pattern matching). Первое условие (pattern) описывает уязвимый паттерн: метод контроллера, принимающий данные из тела HTTP-запроса через атрибут [FromBody], внутри которого поле полученного объекта без какой-либо промежуточной обработки передаётся в метод UpdateEncoderPath.
Второе условие (pattern-not) является исключением: если перед вызовом UpdateEncoderPath в методе присутствует хотя бы один условный блок if, правило не срабатывает. Это позволяет снизить количество ложных срабатываний в тех случаях, когда разработчик добавил явную проверку входного значения перед его использованием.
Встроенные правила SonarQube не обнаружили данную уязвимость. Тем не менее, был выявлен обширный список потенциальных уязвимостей и проблем качества кода.
Было реализовано собственное правило на C# с использованием Roslyn API. Правило реализует сигнатурный анализ без отслеживания потоков данных. Логика основана на двух последовательных проверках, применяемых к каждому объявлению метода в анализируемом коде.
Анализатор обходит список параметров метода и проверяет наличие атрибута [FromBody] или [FromBodyAttribute]. Присутствие этого атрибута означает, что метод является HTTP-эндпоинтом, принимающим данные из тела HTTP-запроса, то есть потенциально контролируемые злоумышленником данные. Если атрибут не обнаружен, дальнейшая проверка не выполняется.
Если первая проверка пройдена, анализатор перебирает все токены-идентификаторы внутри тела метода и сравнивает их с заранее заданным списком имён:
-
ProcessStartInfo — стандартный класс .NET для запуска внешних процессов;
-
UpdateEncoderPath — внутренний метод Jellyfin, устанавливающий путь к исполняемому файлу кодировщика.
Обнаружение любого из этих идентификаторов в методе с [FromBody]-параметром расценивается как потенциальная уязвимость командной инъекции.