JEP draft: Safer Process Launch by ProcessBuilder and Runtime.exec
Более безопасный запуск процессов через ProcessBuilder и Runtime.exec
| Автор | rriggs |
| Ответственный | Roger Riggs |
| Тип | Feature |
| Область | JDK |
| Статус | Draft |
| Выпуск | tbd |
| Компонент | core-libs / java.lang |
| Трудоёмкость | S |
| Длительность | M |
| Создан | 2021/03/16 19:15 |
| Обновлён | 2022/03/24 16:40 |
| Задача | 8263697 |
Аннотация
Повысить безопасность запуска процессов через ProcessBuilder и Runtime.exec в Windows. Аргументы проверяются, чтобы их можно было передать запускаемому приложению без риска разделения или объединения аргументов.
Мотивация
API java.lang.Runtime.exec и java.lang.ProcessBuilder используются для запуска приложения в отдельном процессе операционной системы. В Windows аргументы вызывающей стороны кодируются в одну командную строку, которую затем декодирует приложение. Естественно ожидать, что переданный список аргументов дойдёт до дочернего приложения без изменений. Для большинства операционных систем это так, но в Windows передать произвольную строку не всегда возможно из-за особенностей кодирования и декодирования командной строки.
Подробности кодирования аргументов в Runtime.exec и ProcessBuilder прежде описывались лишь как зависящие от реализации, и разработчикам приходилось самим выяснять, иногда методом проб и ошибок, что работает для их программы. Например, если аргумент содержит или может содержать пробел или табуляцию, приложения охотно заключают аргумент в двойные кавычки. ProcessBuilder уже кодирует аргументы с пробелами и табуляцией, чтобы обеспечить определённую переносимость между операционными системами. Кавычки, добавленные приложением, не нужны и создают неоднозначность: непонятно, предназначены ли сами кавычки для передачи приложению или они нужны только для того, чтобы аргумент был передан как одна строка.
В Windows аргументы собираются и кодируются в одну строку, которая передаётся новому процессу при его создании с помощью CreateProcess. В новой программе аргументы извлекаются из командной строки по одному из распространённых соглашений о значении кавычек, обратных косых черт и специальных символов. Это может приводить к случаям, когда намерение неясно, и создаёт возможность расхождения между кодированием аргументов и разбором командной строки на соответствующие аргументы. Чтобы устранить неоднозначность, кодирование должно быть чётко определено в отношении кавычек и специальных символов и хорошо соответствовать разбору командной строки в приложении.
Например, кавычки обычно сбалансированы, но если в аргументе есть непарные кавычки и аргумент добавляется в командную строку, парной кавычки может не оказаться, и последующие аргументы могут слиться с некорректным аргументом. Например, список из трёх аргументов { "abc", "\"def", "xyz" } можно наивно вставить в командную строку как abc "def xyz, и позже он будет разобран как {"abc", "def xyz"}, то есть три аргумента объединятся в два.
Аналогично, если аргумент равен File with Space and BackSlash\ (без кавычек), итоговую строку командной строки следует заключить в кавычки, чтобы она осталась одной строкой. После добавления первой и последней кавычек строка принимает вид "File with Space and BackSlash\". Выглядит разумно, но нужно посмотреть, как её разберёт приложение. Один из распространённых анализаторов командной строки считает обратную косую черту перед двойной кавычкой литеральной кавычкой, а не открывающей или закрывающей. В этом случае итоговая командная строка разбирается как один аргумент File with Space and BackSlash", который оканчивается кавычкой вместо обратной косой черты.
Наиболее распространённый синтаксис разбора для приложений на C, C++, C# и других языках позволяет корректно кодировать аргументы с пробелами, специальными символами, литеральными кавычками и обратными косыми чертами так, что приложение декодирует их в исходные строки аргументов. Другие программы, такие как .cmd и .bat, командные строки которых обрабатывает командная оболочка, например cmd.exe, используют более простые правила, в которых нет эффективного способа закодировать литеральные кавычки, поэтому некоторые строки аргументов со встроенными кавычками закодировать нельзя. Командная оболочка cmd.exe, которая обрабатывает .cmd и .bat, также неявно включает интерпретацию специальных символов для перенаправления, дающего доступ к файлам, и конвейеров, вызывающих другие программы`. Оба этих случая сомнительного кодирования создают угрозу безопасности, если использовать и проверять их без должной осторожности.
В JDK 18 и более ранних версиях в Windows кодирование аргументов по умолчанию для ProcessBuilder и Runtime.exec довольно нестрогое: допускаются непарные кавычки и специальные символы без кавычек, которые могут объединять или разделять аргументы. Отсутствие проверок допускает различные формы неоднозначных команд, которые невозможно надёжно разобрать, чтобы восстановить исходные аргументы.
Более строгая проверка и кодирование аргументов поддерживаются и в более ранних версиях, но эти функции безопасности нужно включать явно: приложение должно задать системное свойство или использовать security manager. Безопасные режимы помогают приложениям следовать рекомендациям Secure Coding Guidelines for Java SE, чтобы избегать таких рисков, как атаки внедрением и непреднамеренное выполнение. Но большинство разработчиков не пользуются дополнительными проверками безопасности. Изменение поведения по умолчанию на более безопасное может снизить риск неопознанного выполнения и доступа к файлам.
Описание
Мы меняем поведение по умолчанию для ProcessBuilder и Runtime.exec: аргументы в кавычках должны иметь правильно сбалансированные кавычки, а аргументы защищаются от разделения и объединения. Для скриптов, таких как .cmd и .bat, которые выполняются программами-оболочками, кодирование специальных символов, таких как < > & |, изменяется так, чтобы предотвратить неявный доступ к возможностям оболочки, например к перенаправлению и конвейерам. Это ограничение не применяется, если оболочка вызывается явно, например: cmd.exe. Большинство существующих программ работают как прежде, поскольку кодирование аргументов простое и не изменилось. Чтобы отказаться от проверки аргумента, его можно заключить в тройные двойные кавычки. Приложение, отключающее проверку аргументов, необходимо тщательно проверить, чтобы избежать потенциальных угроз безопасности.
Конкретные проверки и кодирование аргументов зависят от того, является ли исполняемый файл . exe или нет. Windows и ProcessBuilder распознают исполняемый файл как .exe, если имя файла оканчивается на .EXE (без учёта регистра) или не содержит точки. Специальные символы для .exe и не-.exe, которые нужно заключать в кавычки, определены Windows для [аргументов командной строки C++] exe-quotes и аргументов Cmd следующим образом:
.exe:spacetab- не-
.exe:spacetab& < > [ ] | { } ^ = ; ! ' + , ~
ProcessBuilder и Runtime.exec выполняют кодирование и передачу аргументов, и приложению не нужно добавлять или изменять аргументы, за исключением нескольких необычных случаев. Настоятельно рекомендуется передавать аргументы, не заботясь о двойных кавычках и специальных символах, и позволить ProcessBuilder выполнить всё необходимое кодирование.
Аргументы кодируются следующим образом:
-
Любой аргумент без двойных кавычек и специальных символов не требует кодирования и передаётся как есть.
-
Любой аргумент, который содержит двойную кавычку или любой из специальных символов, но не начинается с одной двойной кавычки, кодируется добавлением кавычек в начале и в конце. Для программ
.exeвстроенные кавычки и обратные косые черты кодируются по правилам аргументов команды main. Для программ, не являющихся.exe, обратная косая черта не является специальным символом, и аргумент не может содержать кавычки; если кавычки присутствуют, выбрасывается исключение. -
Любой аргумент, начинающийся с одной двойной кавычки, должен иметь правильно сбалансированные двойные кавычки согласно аргументам команды main или cmd.exe Reference — в зависимости от того, является ли файл
.exeили не-.exe. Специальные символы в аргументе, включая пробел и табуляцию, должны находиться внутри двойных кавычек. Если двойные кавычки не парные или специальные символы находятся вне кавычек, выбрасывается исключение. Кавычки гарантируют, что аргумент не будет разделён на два аргумента или объединён с другим аргументом. Для совместимости с более ранними версиями, если перед последней кавычкой стоят одна или несколько обратных косых черт, аргумент кодируется по правилам аргументов команды main. -
Любой аргумент, который начинается и заканчивается тройными двойными кавычками (
"""), вставляется в командную строку без начальных и конечных тройных двойных кавычек. Этот синтаксис «сырой строки» можно использовать, чтобы отказаться от проверки кавычек и специальных символов; он делает особые требования к аргументу заметнее и побуждает к его проверке.
Для команд, не являющихся .exe, обязательное заключение специальных символов (<, >, &, |) в кавычки предотвращает неявное использование перенаправления и конвейеров. Если приложению требуется явно использовать возможности оболочки, оно может вызвать cmd.exe /C с исполняемым файлом и его аргументами. Аргументы проверяются и кодируются так же, как описано выше для приложений .exe. Этот же приём поддерживался и в более ранних версиях. При формировании аргумента, передаваемого оболочке, следует принимать дополнительные меры предосторожности, чтобы избежать непреднамеренных и, возможно, опасных побочных эффектов.
Обратите внимание, что в Linux и macOS символы перенаправления и конвейеров в аргументах не проверяются. Эти символы интерпретируют только командные оболочки, такие как sh, bash или zsh, и они представляют риск, только если исполняемый файл является оболочкой. Обратите внимание, что риск чтения или записи файлов может возникнуть с любой командой через обычный аргумент или параметр команды. Защита, которую даёт ограничение символов перенаправления и конвейеров в Windows, минимальна и неодинакова в разных операционных системах. ProcessBuilder не выполняет никаких проверок или кодирования для конкретных программ ни в одной операционной системе.
Установка security manager никак не влияет на интерпретацию или кодирование аргументов командной строки. Это изменение по сравнению с более ранними версиями, в которых при включённом security manager используется безопасный режим, похожий на jdk.lang.Process.allowAmbiguousCommands=false
. Если security manager включён, проверяется разрешение на выполнение программы; это не изменилось по сравнению с предыдущими версиями.
Примеры
Мотивация этих изменений показала несколько случаев, когда текущее кодирование командной строки в нестрогом режиме может подвергать приложение риску, если оно использует входные данные из окружения или недоверенных источников. Пример ниже показывает, как приложению использовать ProcessBuilder, чтобы избежать этих рисков или снизить их.
Использование списка аргументов вместо одной строки
Чтобы запустить программу java Hello со строкой, содержащей пробелы, используйте отдельные аргументы.
Массив строк проще в использовании и надёжнее, чем одна командная строка. Одну строку нужно тщательно закодировать так, чтобы её можно было декодировать обратно в отдельные аргументы. Приложение становится сложнее, поскольку должно учитывать пробелы, специальные символы и кодирование кавычек. Следующий код может не сработать, если пробелы потеряются, добавятся или специальные символы не будут заключены в кавычки.
String cmd = "java" + " " + "Hello" + " " + "\"Now is the time.\"";
Process p = Runtime.getRuntime().exec(cmd);
Сравните с использованием списка аргументов. Среда выполнения сама кодирует команду и аргументы, содержащие пробелы и кавычки, так что аргументы остаются отдельными и различимыми.
String[] args = {"java", "Hello", "Now is the time."};
Process p = Runtime.getRuntime().exec(args);
-or-
Process p = new ProcessBuilder(args).start();
Включение перенаправления и конвейеров
Чтобы выполнить команду dir и использовать конвейер оболочки для отображения содержимого.
При вызове обычного исполняемого файла, такого как dir, символы не заключаются в кавычки и передаются программе без кавычек. В этом примере аргументы передаются команде dir. dir не является оболочкой и не обрабатывает специальные символы перенаправления или конвейеров. Вывод dir сообщит, что файл «|more» не найден.
List<String args = List.of("dir", "|more");
Process p = new ProcessBuilder(args).start();
Чтобы выводить список каталога постранично, в качестве исполняемого файла используется cmd.exe.
cmd.exe вызывает команду dir и интерпретирует специальный символ, чтобы передать вывод по конвейеру в more.
List<String args = List.of("cmd.exe", "/C", "dir", "|more");
Process p = new ProcessBuilder(args).start();
Перенаправление и конвейеры в скриптах .cmd и .bat
Чтобы запустить скрипт .cmd или .bat и перенаправить вывод в файл.
Если исполняемый файл — это .cmd или .bat, а не .exe, аргументы со специальными символами заключаются в кавычки, чтобы они неявно не вызывали перенаправление, конвейеры или групповое выполнение. В этом случае скрипту будет передана строка">log.out", и перенаправления не произойдёт.
List<String args = List.of("log.cmd", ">log.out");
Process p = new ProcessBuilder(args).start();
Чтобы перенаправить вывод журнала, в качестве исполняемой оболочки используется cmd.exe, которой передаются log.cmd как команда для запуска и нужное перенаправление.
List<String args = List.of("cmd.exe", "/C", "log.cmd", ">log.out");
Process p = new ProcessBuilder(args).start();
Кодирования командной строки по умолчанию достаточно
Передача приложению имени каталога с пробелами.
Типичный путь к каталогу может содержать или не содержать пробелы и заканчиваться обратной косой чертой («\»). Простой код работает независимо от того, является ли приложение .exe или не-.exe. Примечание: двойная обратная косая черта в исходном коде соответствует одной обратной косой черте в строке Java.
List<String args = List.of("cmd.exe", "/C", "do.cmd", "C:\\Program Files\\");
Process p = new ProcessBuilder(args).start();
ProcessBuilder кодирует аргумент (из-за пробела), заключая его в двойные кавычки. Число обратных косых черт перед кавычкой удваивается, как того требует кодирование командной строки C++, чтобы кавычка считалась парной закрывающей кавычкой, а не буквальной кавычкой. Теперь аргумент в командной строке выглядит так:"C:\\Program Files\\\\"
Если приложение — исполняемый файл .exe, командная строка разбирается, и удвоение обратных косых черт перед закрывающей кавычкой отменяется, что даёт исходный аргумент "C:\\Program Files\\".
Если приложение — скрипт .cmd, строка аргумента содержит кавычки и дополнительную обратную косую черту: "\"C:\\Program Files\\\\\". Поскольку аргумент — это путь к каталогу Windows, добавленная обратная косая черта безвредна и игнорируется при операциях с файлами.
Совместимость
Аргументы команды, которые не содержат двойных кавычек или специальных символов, работают как раньше: изменений нет, и специальное кодирование не требуется. Аргументы, содержащие пробелы или табуляции, тоже работают как раньше. Самый распространённый случай для существующих приложений — нестрогий режим, в котором не проверялись ни парность кавычек, ни использование специальных символов. Единственное кодирование заключалось в добавлении кавычек к аргументам, содержащим пробел или табуляцию. Хотя у большинства приложений аргументы корректны, с этими изменениями для некорректных аргументов выбрасываются исключения; приложение следует исправить так, чтобы кавычки были парными.
В нестрогом режиме предыдущих выпусков кодирование аргументов программ .exe и не-. exe не различалось в отношении специальных символов. С этими изменениями аргументы для не-.exe программ, содержащие символы space tab & < > [ ] | { } ^ = ; ! ' + , ~, заключаются в кавычки. Уже имеющиеся в аргументах двойные кавычки, если они парные, остаются как есть. Аргументы со встроенными кавычками, которые нужно передать как буквальные кавычки, закодировать нельзя, и будет выброшено исключение. Дополнительный запасной вариант — вызвать оболочку, например cmd.exe, и передать аргументы, как описано выше.
Для программ, которые нельзя обновить, обратная совместимость с более нестрогим режимом достигается установкой системного свойства jdk.lang.Process.allowAmbiguousCommands=true. Свойство задаётся в командной строке, его нельзя изменить с помощью System.setProperty, и оно применяется ко всем использованиям ProcessBuilder.
Существующие приложения можно проверить на совместимость на текущих выпусках, задав jdk.lang.Process.allowAmbiguousCommands=false в командной строке. В JDK 18 и более ранних версиях это включает более строгую проверку кавычек и специальных символов. Примечание: в этом предложении набор специальных символов для не-.exe программ расширен.
В операционных системах Linux и macOS системное свойство jdk.lang.Process.allowAmbiguousCommands не используется, и аргументы передаются буквально.
Тестирование
Существующие тесты будут обновлены для проверки новых вариантов кодирования. Тесты совместимости подтвердят, что нестрогий режим, включённый с помощью jdk.lang.Process.allowAmbiguousCommands = true, ведёт себя так же, как в предыдущих версиях JDK.
Команды будут протестированы для сценариев .exe, не-exe и cmd.exe /C.
Риски и допущения
Передача аргументов при запуске процесса сильно зависит от интерпретации кавычек и специальных символов в параметрах приложения, последующего кодирования этих параметров в командную строку Windows и соответствующего разбора командной строки для восстановления аргументов. Есть риск, что более строгая интерпретация приведёт к исключениям в случаях, которые раньше были допустимы, или командная строка будет закодирована иначе.
Интерпретация кавычек и специальных символов очень близка к нестрогому режиму — самому частому сценарию при отсутствии security manager. Нестрогий режим можно снова включить, установив системное свойство jdk.lang.Process.allowAmbiguousCommands.