Как безопасно передать произвольный текст в качестве параметра в программу в сценарии оболочки? - PullRequest
0 голосов
/ 18 ноября 2018

Я пишу приложение с графическим интерфейсом для распознавания символов, которое использует Tesseract. Я хочу разрешить пользователю указывать пользовательскую команду оболочки, которая будет выполняться с /bin/sh -c, когда текст будет готов. Проблема в том, что распознанный текст может содержать буквально все, например && rm -rf some_dir.

Моя первая мысль была сделать это, как во многих других программах, где пользователь может ввести команду в текстовой записи, а затем специальные строки (как в printf()) в команде заменяются соответствующими данными (в моем случае это может быть %t). Затем вся строка передается в execvp(). Например, вот скриншот из qBittorrent: enter image description here

Проблема в том, что даже если я правильно экранирую текст перед заменой %t, ничто не мешает пользователю добавлять дополнительные кавычки вокруг спецификатора:

echo '%t' >> history.txt

Итак, полная команда, которую нужно выполнить:

echo ''&& rm -rf some_dir'' >> history.txt

Очевидно, это плохая идея.

Опция секунда позволяет пользователю выбрать только исполняемый файл (с диалоговым окном выбора файлов), поэтому я могу вручную поместить текст из Tesseract как argv[1] для execvp(). Идея состоит в том, что исполняемый файл может быть скриптом, в который пользователи могут помещать все, что хотят, и получать доступ к тексту с помощью "$1". Таким образом, внедрение команды невозможно (я думаю). Вот пример сценария, который может создать пользователь:

#!/bin/sh
echo "$1" >> history.txt

Есть ли какие-нибудь подводные камни при таком подходе? Или, может быть, есть лучший способ безопасно передать произвольный текст в качестве параметра программе в сценарии оболочки?

Ответы [ 2 ]

0 голосов
/ 18 ноября 2018

In-Band: экранирование произвольных данных в контексте без кавычек

Не делай этого. См. Раздел «Out-of-Band» ниже.

Чтобы произвольная строка C (не содержащая NUL) оценивалась сама по себе при использовании в контексте без кавычек в строго POSIX-совместимой оболочке, вы можете использовать следующие шаги:

  • Добавить ' (переход от необходимого начального без кавычек контекста к контексту в одинарных кавычках).
  • Заменить каждый литерал ' в данных строкой '"'"'. Эти персонажи работают следующим образом:
    1. ' закрывает начальный контекст в одинарных кавычках.
    2. " входит в двойные кавычки.
    3. ' в двойных кавычках является литералом.
    4. " закрывает контекст в двойных кавычках.
    5. ' повторно входит в одинарные кавычки.
  • Добавить ' (возврат к необходимому начальному контексту в одинарных кавычках).

Это работает правильно в POSIX-совместимой оболочке, потому что единственный символ, который не является литералом внутри контекста в одинарных кавычках, это '; в этом контексте даже обратные слеши считаются буквальными.

Тем не менее, это работает правильно только тогда, когда сигилы используются только в не заключенном в кавычки контексте (таким образом, на ваших пользователей возлагается бремя, чтобы все было правильно), и когда оболочка строго POSIX-совместима. Кроме того, в худшем случае строка, сгенерированная этим преобразованием, может быть в 5 раз длиннее исходной; таким образом, нужно быть осторожным с распределением памяти, используемой для преобразования.

(Можно спросить, почему вместо '\'' рекомендуется '"'"'; это потому, что обратные слеши меняют свое значение, используемое в устаревшем синтаксисе подстановки команд backtick, поэтому более длинная форма более устойчива).


Out-of-Band: переменные среды или аргументы командной строки

Данные должны передаваться только вне диапазона из кода, чтобы они вообще никогда не проходили через анализатор. При вызове оболочки есть два простых способа сделать это (кроме использования файлов): переменные среды и аргументы командной строки.

В обоих нижеприведенных механизмах нужно доверять только user_provided_shell_script (хотя для этого также необходимо, чтобы ему доверяли не вносить новые или дополнительные уязвимости; использование eval или любого другого морального эквивалента аннулирует все гарантии, но это проблема пользователя, а не ваша).

Использование переменных среды

Исключая обработку ошибок (если setenv() возвращает ненулевой результат, это следует рассматривать как ошибку, а perror() или аналогичный следует использовать для сообщения пользователю), это будет выглядеть следующим образом:

setenv("torrent_name", torrent_name_str, 1);
setenv("torrent_category", torrent_category_str, 1);
setenv("save_path", path_str, 1);

# shell script should use "$torrent_name", etc
system(user_provided_shell_script);

Несколько заметок:

  • Хотя значения могут быть произвольными строками C, важно, чтобы имена переменных были ограничены - либо жестко закодированные константы, как указано выше, либо префикс с константной (строчной 7-битной ASCII) строкой и проверенный на наличие только символов, которые являются допустимой оболочкой имена переменных. (Рекомендуется использовать префикс в нижнем регистре, поскольку POSIX-совместимые оболочки используют только имена всех заглавных букв для переменных, которые изменяют их собственное поведение; см. спецификацию POSIX для переменных среды , особенно примечание, что " пространство имен имен переменных среды, содержащих строчные буквы, зарезервировано для приложений. Приложения могут определять любые переменные среды с именами из этого пространства имен без изменения поведения стандартных утилит ").
  • Пространство среды является ограниченным ресурсом; в современном Linux максимальное объединенное хранилище для переменных среды и аргументов командной строки обычно составляет 128 КБ; таким образом, установка больших переменных среды приведет к сбою семейных вызовов execve() с большими командными строками. Мудро подтвердить, что длина находится в разумных пределах для конкретной области.

Использование аргументов командной строки:

Эта версия требует явного API, так что пользователь, конфигурирующий команду триггера, знает, какое значение будет передано в $1, которое будет передано в $2 и т. Д.

/* You'll need to do the usual fork() before this, and the usual waitpid() after
 * if you want to let it complete before proceeding.
 * Lots of Q&A entries on the site already showing the context.
 */
execl("/bin/sh", "-c", user_provided_shell_script,
  "sh",                 /* this is $0 in the script */
  torrent_name_str,     /* this is $1 in the script */
  torrent_category_str, /* this is $2 in the script */
  path_str,             /* this is $3 in the script */
  NUL);
0 голосов
/ 18 ноября 2018

В любое время, когда вы запускаете команды, даже имея возможность ввода данных пользователем, вы должны скрыться за контекстом оболочки.

В C нет встроенной функции для этого, так что вы по своему усмотрению, но основная идея состоит в том, чтобы отображать пользовательские параметры либо как правильно экранированные строки, либо как отдельные аргументы для какой-либо функции выполнения (например, exec семейство ).

...