"Запустити будь-яку команду, яка передасть недовірені дані командам, які інтерпретують аргументи як команди"


18

З посібника з findutils:

Наприклад такі конструкції, як ці дві команди

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

дуже небезпечні. Причиною цього є те, що "{}" розширено на ім'я файлу, яке може містити крапку з комою або інші символи, спеціальні для оболонки. Якщо, наприклад, хтось створює файл, /tmp/foo; rm -rf $HOMEто дві вищевказані команди можуть видалити чиїсь домашні каталоги.

Тому з цієї причини не запускайте жодної команди, яка передасть недовірені дані (наприклад, імена fi les) командам, які інтерпретують аргументи як команди, які слід далі інтерпретувати (наприклад, "sh").

Що стосується оболонки, то для цієї проблеми є розумне рішення:

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

Цей підхід не гарантується, щоб уникнути кожної проблеми, але він набагато безпечніший, ніж заміщення даних вибору зловмисника в текст команди оболонки.

  1. Чи є причиною проблеми в find -exec sh -c "something {}" \;тому, що заміна на {}не цитується і тому не трактується як одна рядок?
  2. У рішенні find -exec sh -c 'something "$@"' sh {} \;,

    • спочатку {}замінюється, але оскільки {}це не цитується, чи не "$@"виникає така ж проблема, як оригінальна команда? Наприклад, "$@"буде розширена "/tmp/foo;", "rm", "-rf"і "$HOME"?

    • чому {}не уникнути і не цитувати?

  3. Чи можете ви навести інші приклади (як і раніше sh -c, або без цього, якщо це застосовується; з чи без findцього, можливо, не потрібно), коли застосовуються однакові проблеми та рішення, і які є мінімальними прикладами, щоб ми могли зосередити увагу на проблемі та вирішенні якомога менше відволікання? Див. Розділи Способи надання аргументів команді, виконаній `bash -c`

Спасибі.


Відповіді:


29

Це насправді не пов’язано з цитуванням, а скоріше з обробкою аргументів.

Розглянемо ризикований приклад:

find -exec sh -c "something {}" \;
  • Це обробляється інтерпретатором, і розбитий на шість слів: find, -exec, sh, -c, something {}(без лапок більше), ;. Нема чого розширювати. Оболонка працює findз цими шістьма словами як аргументи.

  • Коли findщо - то знаходить в процесі, скажімо foo; rm -rf $HOME, він замінює {}з foo; rm -rf $HOME, і працює shз аргументами sh, -cі something foo; rm -rf $HOME.

  • shтепер бачить -c, і в результаті аналізує something foo; rm -rf $HOME( перший аргумент, що не є варіантом ) та виконує результат.

Тепер розглянемо більш безпечний варіант:

find -exec sh -c 'something "$@"' sh {} \;
  • Оболонка працює findз аргументами find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Тепер , коли findзнахідки foo; rm -rf $HOME, він замінює {}знову, і працює shз аргументами sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • shбачить -c, і аналізує , something "$@"як команду для запуску, а також shі foo; rm -rf $HOMEяк позиційні параметри ( починаючи з$0 ), розширюється "$@"до foo; rm -rf $HOME як одне значення , і працює somethingз одним аргументом foo; rm -rf $HOME.

Ви можете побачити це за допомогою printf. Створіть новий каталог, введіть його та запустіть

touch "hello; echo pwned"

Перший варіант працює наступним чином

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

виробляє

Argument: .
Argument: ./hello
pwned

тоді як другий варіант працює як

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

виробляє

Argument: .
Argument: ./hello; echo pwned

Чому в безпечнішому варіанті чомусь {}не уникнути і не цитувати?
Тім

1
Це взагалі не потрібно цитувати; його потрібно було б цитувати лише в оболонці там, де він має якесь інше значення, і я не думаю, що це стосується будь-якої з основних оболонок, які зараз використовуються.
Стівен Кітт

З gnu.org/software/findutils/manual/html_mono/… : "Обидві ці конструкції ( ;і {}) потрібно уникнути (з" \ ") або процитувати, щоб захистити їх від розширення оболонкою." Ви маєте на увазі, що це неправильно для bash?
Тім

3
Я маю на увазі, що це взагалі неправильно для снарядів. Див. POSIX . Ця конкретна заява в findutilsпосібнику датується щонайменше 1996 р. ... Звісно, ​​цитувати це не шкодить {}, як '{}'або "{}", але це не потрібно.
Стівен Кітт

@Tim У вас виникає звичайна ситуація, коли ваша інтуїція ще не пояснює той факт, що тут відбувається два дуже різні типи обробки аргументів: коли / як оболонка аналізує аргументи з рядка тексту (який там, де цитування має значення) відрізняється від перегляду аргументу необробленої операційної системи (де лапки є лише звичайними символами). У команді оболонки find some/path -exec sh -c 'something "$@"' {} \;насправді є три шари обробки / передачі аргументів, два з різновидів оболонок та один із основних неочищених різновидів операційної системи.
mtraceur

3

Частина 1:

find просто використовує заміну тексту.

Так, якщо ви зробили це без котирування, ось так:

find . -type f -exec sh -c "echo {}" \;

і зловмисник зміг створити файл, який називається ; echo owned, тоді він буде виконаний

sh -c "echo ; echo owned"

що призвело б до оболонки працює echoтоді echo owned.

Але якщо ви додали котирування, зловмисник може просто закінчити ваші котирування, а потім поставити після нього зловмисну ​​команду, створивши файл під назвою '; echo owned:

find . -type f -exec sh -c "echo '{}'" \;

що призведе в управлінні оболонки echo '', echo owned.

(якщо ви поміняли подвійні лапки на одиничні лапки, зловмисник також може використати інший тип лапок.)


Частина 2:

У find -exec sh -c 'something "$@"' sh {} \;, команда {}спочатку не інтерпретується оболонкою, вона виконується безпосередньо execve, тому додавання цитат оболонок не допоможе.

find -exec sh -c 'something "$@"' sh "{}" \;

не має ефекту, оскільки оболонка знімає подвійні лапки перед запуском find.

find -exec sh -c 'something "$@"' sh "'{}'" \;

додає лапки, що оболонка не обробляє спеціально, тому в більшості випадків це просто означає, що команда не буде робити те, що ви хочете.

Маючи це розширюватися /tmp/foo;, rm, -rf, $HOMEне повинно бути проблемою, тому що ті аргументи something, і , somethingймовірно , не відноситься до його аргументи як команди для виконання.


Частина 3:

Я припускаю, що подібні міркування застосовуються до всього, що приймає ненадійний вхід і виконує його як (частину) команди, наприклад xargsі parallel.


xargsнебезпечний лише за умови використання -Iабо -Jвикористання. У звичайній роботі це лише додавання аргументів до кінця списку, як -exec ... {} +і у випадку.
Чарльз Даффі

1
( parallelнавпаки, запускає оболонку за замовчуванням без явного запиту користувача, збільшуючи вразливу поверхню; це питання, яке було предметом багато обговорень; див. для пояснення див. unix.stackexchange.com/questions/349483/… , і включене посилання на списки.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html ).
Чарльз Даффі

@CharlesDuffy Ви маєте на увазі " зменшення вразливої ​​поверхні". Напад, проілюстрований ОП, дійсно не працює за замовчуванням у GNU Parallel.
Оле Танге

1
Так, я знаю, що вам це потрібно для паритету в SSH - якщо ви не породили процес віддаленого parallel"приймача" на іншому кінці будь-якого сокета, здатний зробити прямий без оболонки execv. Це саме те, що я зробив би, якби у вашому взутті реалізували цей інструмент. Наявність неінтерактивної програми поводиться по-різному, виходячи з поточного значення SHELL, навряд чи є подив.
Чарльз Даффі

1
Так - я вважаю, що труби та перенаправлення повинні бути неможливими, якщо користувач явно не запустить оболонку, і тоді він отримає лише явну поведінку оболонки, яку явно запустив. Якщо можна очікувати лише того, що execvзабезпечує, це простіше і менш дивовижно, і правило, яке не змінюється в різних місцях / середовища виконання.
Чарльз Даффі

3

1. Чи є причина проблеми в find -exec sh -c "something {}" \;тому, що заміна на {}не цитується і тому не трактується як одна рядок?

У певному сенсі, але цитування тут не може допомогти . Ім'я файла, яке замінюється замість, {}може містити будь-які символи, включаючи лапки . Яка б форма котирування не використовувалася, ім'я файлу могло б містити те саме і "вибиватися" з лапок.

2. ... але оскільки {}це не цитується, чи не "$@"виникає така ж проблема, як у вихідній команді? Наприклад, "$@"буде розширено до "/tmp/foo;", "rm", "-rf", and "$HOME"?

"$@"розширюється на позиційні параметри, як окремі слова, і не розділяє їх далі. Тут {}є findсамим аргументом і findпередає поточне ім'я файлу також як окремий аргумент sh. Він прямо доступний як змінна в сценарії оболонки, не обробляється як сама команда оболонки.

... чому {}не уникнути і не цитувати?

У більшості оболонок цього не потрібно. Якщо ви запустите fish, це повинно бути: fish -c 'echo {}'друкує порожній рядок. Але не має значення, чи цитуєте ви його, оболонка просто видалить лапки.

3. Чи можете ви навести інші приклади ...

Кожного разу, коли ви розширюєте ім’я файлу (або інший неконтрольований рядок) як є всередині рядка, який приймається як якийсь код (*) , існує можливість довільного виконання команди.

Наприклад, це розширює $fбезпосередньо код Perl і спричинить проблеми, якщо ім'я файлу містить подвійну лапочку. Цитата в імені файлу закінчить цитату в коді Perl, а решта імені файлу може містити будь-який код Perl:

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(Ім'я файлу повинно бути трохи дивним, оскільки Perl аналізує весь код на передній частині, перш ніж запустити будь-який з них. Тому нам доведеться уникати помилки розбору.)

Хоча це безпечно передається через аргумент:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(Немає сенсу запускати іншу оболонку безпосередньо з оболонки, але якщо це зробити, вона схожа на find -exec sh ...випадок)

(* якийсь код включає SQL, тому обов'язковий XKCD: https://xkcd.com/327/ плюс пояснення: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )


1

Ваша стурбованість є саме причиною, чому GNU Parallel цитує вхід:

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

Це не запуститься echo pwned.

Він запустить оболонку, так що якщо ви розширите свою команду, ви не зненацька отримаєте сюрприз:

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

Докладніші відомості про проблему з нерестовою оболонкою див: https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell


1
... що сказане, find . -print0 | xargs -0 printf "Argument: %s\n"так само безпечно (точніше, більше того, оскільки -print0одна обробляє файли з новими рядками правильно); Цитування паралельних є вирішенням проблеми, яка взагалі не існує, коли немає оболонки.
Чарльз Даффі

Але як тільки ви додасте | wcдо команди, вам потрібно перестрибнути обручі, щоб зробити це безпечним. GNU Parallel є безпечним за замовчуванням.
Оле Танге

ITYM: він намагається охопити кожну вигадку мови оболонки складним процесом ретельного цитування всього. Це не так безпечно, як правильне зберігання даних у змінних, не змішаних з кодом. Тепер, якби автоматично перетворити це foo {} | wcв sh -c 'foo "$1" | wcsh {} `, це може бути інакше.
ilkkachu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.