Преамбула
По-перше, я б сказав, що це не правильний спосіб вирішення проблеми. Це трохи схоже на те, щоб сказати: " не слід вбивати людей, бо інакше ти потрапиш до в'язниці ".
Так само ви не цитуєте змінну, оскільки в іншому випадку ви вводите вразливі місця безпеки. Ви цитуєте свої змінні, оскільки це неправильно не робити (але якщо страх перед в'язницею може допомогти, чому б і ні).
Невелике резюме для тих, хто щойно заскочив у поїзд.
У більшості оболонок залишення змінної розширення без котирування (хоча це (і решта цієї відповіді) також стосується підстановки команд ( `...`
або $(...)
), а арифметичне розширення ( $((...))
або $[...]
)) має дуже особливе значення. Найкращий спосіб описати це, як це викликати якийсь неявний спліт + глобальний оператор¹.
cmd $var
іншою мовою було б написано щось на кшталт:
cmd(glob(split($var)))
$var
спочатку розбивається на список слів за складними правилами, що включають $IFS
спеціальний параметр ( розділена частина), а потім кожне слово, що є результатом цього розщеплення, розглядається як візерунок, який розширюється до списку файлів, що відповідають йому ( глобальна частина) .
Як приклад, якщо він $var
містить *.txt,/var/*.xml
і $IFS
містить ,
, cmd
буде названо ряд аргументів, перший з них - cmd
наступний - txt
файли в поточному каталозі та xml
файли в /var
.
Якщо ви хочете зателефонувати cmd
лише з двома буквальними аргументами cmd
і *.txt,/var/*.xml
, ви напишете:
cmd "$var"
яка була б вашою іншою більш звичною мовою:
cmd($var)
Що ми маємо на увазі під вразливістю в оболонці ?
Зрештою, ще з світанку відомо, що сценарії оболонки не повинні використовуватися в контекстах, що залежать від безпеки. Звичайно, гаразд, залишення змінної без котирування - це помилка, але це не може принести такої шкоди, чи не так?
Ну, незважаючи на те, що хтось скаже вам, що сценарії оболонки ніколи не повинні використовуватися для веб-CGI, або що, на щастя, більшість систем не дозволяють встановити / встановити жорсткі сценарії оболонок в даний час, одна річ, що дозволяє скористатися обробкою оболонок заголовки в вересні 2014 г.) показали, що шкаралупа по - , як і раніше широко використовується там , де вони , ймовірно , не слід: у CGIs, в DHCP - клієнт скрипти пасток, в sudoers команд, що викликаються з допомогою (якщо не як ) Setuid команди ...
Іноді несвідомо. Наприклад, system('cmd $PATH_INFO')
в php
/ perl
/ python
CGI-скрипті викликає оболонку для інтерпретації цього командного рядка (не кажучи вже про те, що cmd
сам по собі може бути сценарієм оболонки, і його автор, можливо, ніколи не очікував, що він буде викликаний із CGI).
У вас є вразливість, коли є шлях до ескалації привілеїв, тобто коли хтось (давайте назвемо його зловмисником ) здатний зробити щось, чого йому не призначено.
Це незмінно означає, що зловмисник надає дані, що дані обробляються привілейованим користувачем / процесом, який ненавмисно робить щось, чого не повинен робити, в більшості випадків через помилку.
В основному у вас виникають проблеми, коли ваш баггі-код обробляє дані під контролем зловмисника .
Тепер не завжди очевидно, звідки ці дані можуть взятися, і часто важко сказати, чи дійде ваш код колись обробляти ненадійні дані.
Що стосується змінних, то у випадку сценарію CGI - це цілком очевидно, що дані - це параметри CGI GET / POST і такі речі, як файли cookie, шлях, хост ... параметри.
Для встановленого сценарію (працює як один користувач, коли викликається іншим), це аргументи або змінні середовища.
Ще один дуже поширений вектор - це імена файлів. Якщо ви отримуєте список файлів з каталогу, можливо, файли там посадили зловмисники .
У зв'язку з цим , навіть в рядку інтерактивної оболонки, ви могли б бути уразливим (при обробці файлів в /tmp
або ~/tmp
, наприклад).
Навіть ~/.bashrc
може бути вразливим (наприклад, bash
буде інтерпретувати його, коли викликається, ssh
щоб запустити ForcedCommand
подібне в git
серверних розгортаннях з деякими змінними під контролем клієнта).
Тепер сценарій не може бути викликаний безпосередньо для обробки недовірених даних, але він може бути викликаний іншою командою, яка це робить. Або ваш неправильний код може бути скопійований у сценарії, які ви робите (ви 3 роки внизу лінії чи хтось із ваших колег). Одне місце, де це особливо важливо, - це відповіді на веб-сайтах з питань запитання, оскільки ви ніколи не дізнаєтесь, де можуть потрапити копії коду.
Вниз до бізнесу; як це погано?
Залишення змінної (або підстановки команд) без котирування - це далеко не перше джерело вразливості безпеки, пов'язаних з кодом оболонки. Частково тому, що ці помилки часто перекладаються на вразливості, але також тому, що так часто зустрічаються змінні, що не котируються.
Насправді, шукаючи вразливості коду оболонки, перше, що потрібно зробити, - це шукати змінні, що не котируються. Це легко помітити, часто хороший кандидат, як правило, легко відстежувати дані, керовані зловмисниками.
Існує нескінченна кількість способів, коли котирувана змінна може перетворитись на вразливість. Я лише наведу кілька загальних тенденцій.
Розкриття інформації
Більшість людей стикаються з помилками, пов’язаними із змінними, що не котируються, через розділену частину (наприклад, для файлів у наш час є пробіли, а простір у значенні за замовчуванням IFS). Багато людей не
помітять глобальної частини. Частина глобуса принаймні така ж небезпечна, як і
розділена частина.
Глобінг, зроблений при несанітизованому зовнішньому введенні, означає, що зловмисник може змусити вас читати вміст будь-якого каталогу.
В:
echo You entered: $unsanitised_external_input
якщо він $unsanitised_external_input
містить /*
, це означає, що зловмисник може бачити вміст /
. Нічого страшного. Це стає цікавішим, хоча з цим /home/*
ви отримуєте список імен користувачів на машині /tmp/*
, /home/*/.forward
для підказки щодо інших небезпечних практик, /etc/rc*/*
для ввімкнених служб ... Не потрібно називати їх окремо. Значення /*
/*/* /*/*/*...
буде просто перераховувати всю файлову систему.
Відмова від уразливості сервісу.
Попередній випадок було занадто далеко, і ми отримали DoS.
Насправді, будь-яка котирувана змінна в контексті списку з несанітованим введенням є принаймні вразливістю DoS.
Навіть експертні скрипти для оболонок зазвичай забувають цитувати такі речі, як:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
- команда без операції. Що може піти не так?
Це означало , щоб призначити $1
для $QUERYSTRING
якщо $QUERYSTRING
було відключено. Це швидкий спосіб зробити CGI-скрипт, який можна дзвонити з командного рядка.
Це $QUERYSTRING
все ще розширено, і тому що це не цитується, викликається оператор split + glob .
Зараз є деякі глобуси, які особливо дорого розширюються. /*/*/*/*
Один досить погано , як це означає , що лістинг каталогів до 4 -х рівнів вниз. На додаток до активності диска та процесора, це означає зберігання десятків тисяч файлових шляхів (40 к тут на мінімальному сервері VM, 10 к. З яких каталоги).
Зараз це /*/*/*/*/../../../../*/*/*/*
означає 40k x 10k і
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
цілком достатньо, щоб поставити на коліна навіть найсильнішу машину.
Спробуйте самі (хоча будьте готові до того, що ваша машина вийде з ладу або зависне):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Звичайно, якщо код:
echo $QUERYSTRING > /some/file
Потім ви можете засипати диск.
Просто виконайте пошук Google на оболонках cgi або bash cgi або ksh cgi , і ви знайдете кілька сторінок, які показують, як писати CGI в оболонках. Зверніть увагу, наскільки половина з тих, хто параметри процесу є вразливими.
Навіть сам Девід Корн
вразливий (дивіться на обробку файлів cookie).
аж до довільних уразливостей виконання коду
Довільне виконання коду є найгіршим типом вразливості, оскільки якщо зловмисник може виконати будь-яку команду, обмежень у тому, що він може робити, немає.
Це взагалі розділена частина, яка призводить до цих. Це розщеплення призводить до декількох аргументів, які передаються командам, коли очікується лише один. У той час як перші з них будуть використовуватися в очікуваному контексті, а інші будуть в іншому контексті, тому потенційно трактуються по-різному. Краще з прикладом:
awk -v foo=$external_input '$2 == foo'
Тут наміром було призначити зміст
$external_input
оболонки foo
awk
змінної.
Зараз:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Друге слово в результаті розщеплення $external_input
не присвоюється, foo
а вважається awk
кодом (тут виконується довільна команда:) uname
.
Це особливо проблема для команд , які можуть виконуватися інші команди ( awk
, env
, sed
(GNU один) perl
, find
...) , особливо з варіантами GNU (які приймають параметри після аргументів). Іноді ви не підозрюєте команди, щоб мати змогу виконувати такі ksh
, як , bash
або zsh
s [
або printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Якщо ми створимо каталог, який називається x -o yes
, то тест стає позитивним, оскільки ми оцінюємо це зовсім інший умовний вираз.
Гірше, якщо ми створимо файл, який називається x -a a[0$(uname>&2)] -gt 1
, з усіма ksh реалізаціями принаймні (що включає sh
більшість комерційних Unices та деяких BSD), який виконується, uname
оскільки ці оболонки виконують арифметичну оцінку на операторах порівняння чисел [
команди.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
Те саме з bash
іменем файлу, як x -a -v a[0$(uname>&2)]
.
Звичайно, якщо вони не можуть отримати довільну страту, зловмисник може погодитися на менший збиток (що може допомогти отримати довільну страту). Будь-яка команда, яка може записувати файли або змінювати дозволи, права власності або мати будь-який головний чи побічний ефект, може бути використана.
Всілякі речі можна зробити з іменами файлів.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
І ви закінчуєте робити ..
запис (рекурсивно з GNU
chmod
).
Сценарії, які виконують автоматичну обробку файлів у публічно доступних для запису областях, як /tmp
, наприклад , повинні писатися дуже ретельно.
Що стосовно [ $# -gt 1 ]
Це те, що мені дратує. Деякі люди стикаються з усіма проблемами, цікавлячись, чи може бути конкретним розширенням проблематичне рішення, чи можуть вони пропустити цитати.
Це як сказати. Гей, схоже, $#
не може бути підпорядкований оператор split + glob, давайте попросимо оболонку розділити + glob . Або Ей, давайте запишемо невірний код лише тому, що помилка навряд чи потрапить .
Тепер наскільки це малоймовірно? ОК, $#
(або $!
, $?
або яка - або арифметична заміщення) може містити тільки цифри (або -
для деяких) , так що Глоб частина виходить. Щоб розділена частина щось зробила, все, що нам потрібно, - $IFS
це містити цифри (або -
).
Деякі оболонки $IFS
можуть успадковуватися від навколишнього середовища, але якщо навколишнє середовище не є безпечним, це все-таки гра.
Тепер, якщо ви пишете таку функцію, як:
my_function() {
[ $# -eq 2 ] || return
...
}
Це означає, що поведінка вашої функції залежить від контексту, в якому вона викликана. Або іншими словами, $IFS
стає одним із входів до цього. Власне кажучи, коли ви пишете документацію API для своєї функції, це має бути щось на зразок:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
І для виклику коду вашої функції потрібно переконатися, що $IFS
вона не містить цифр. Все це тому, що вам не здалося, що ви вводите ці два символи з подвійною цитатами.
Тепер, щоб ця [ $# -eq 2 ]
помилка стала вразливістю, вам знадобиться якось значення, $IFS
щоб стати під контролем зловмисника . Імовірно, це зазвичай не відбудеться, якщо зловмиснику не вдасться використати чергову помилку.
Але це не нечувано. Поширений випадок, коли люди забувають санітувати дані, перш ніж використовувати їх в арифметичному вираженні. Вище ми вже бачили, що він може дозволити довільне виконання коду в деяких оболонках, але у всіх з них він дозволяє
зловмиснику надати будь-якій змінній ціле значення.
Наприклад:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
І при $1
значенні a (IFS=-1234567890)
, що арифметична оцінка має побічний ефект від параметрів IFS, і наступна [
команда виходить з ладу, що означає, що перевірка на занадто багато аргументів обходить.
Що робити, коли оператор split + glob не викликається?
Є ще один випадок, коли необхідні лапки навколо змінних та інших розширень: коли вони використовуються як візерунок.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
не відчувають Чи $a
й $b
ті ж ( за винятком zsh
) , але якщо $a
відповідає шаблоном в $b
. І вам потрібно процитувати , $b
якщо ви хочете , щоб порівняти , як рядки ( то ж саме в "${a#$b}"
або "${a%$b}"
або "${a##*$b*}"
де $b
повинні бути вказані , якщо це не слід сприймати як шаблон).
Це означає, що [[ $a = $b ]]
може повернути істину у випадках, коли $a
вони відрізняються від $b
(наприклад, коли $a
є anything
і $b
є *
), або можуть повернути помилкові, коли вони однакові (наприклад, коли вони є $a
і $b
є [a]
).
Чи може це спричинити вразливість безпеки? Так, як і будь-яка помилка. Тут зловмисник може змінити потік логічного коду вашого сценарію та / або порушити припущення, які створює ваш сценарій. Наприклад, з таким кодом:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
Зловмисник може обійти чек, проходячи повз '[a]' '[a]'
.
Тепер, якщо не застосовується ні той шаблон, який не відповідає, ні оператор split + glob , яка небезпека залишити змінну без котирування?
Я повинен визнати, що я пишу:
a=$b
case $a in...
Там цитування не шкодить, але не є строго необхідним.
Однак, одним із побічних ефектів пропущення цитат у цих випадках (наприклад, у відповідях на запитання та відповіді) є те, що він може надіслати помилкове повідомлення початківцям: що це може бути нормально, не цитувати змінні .
Наприклад, вони можуть почати думати, що якщо a=$b
це нормально, тоді export a=$b
це буде так само (що це не у багатьох оболонках, як це в аргументах до export
команди так у контексті списку) або env a=$b
.
Про що zsh
?
zsh
виправили більшість цих незручностей у дизайні. У zsh
(принаймні, коли він не знаходиться в режимі емуляції sh / ksh), якщо ви хочете розщеплення , глобулювання або узгодження шаблону , вам потрібно його чітко запитати: $=var
розділити, а $~var
також глобул або вміст змінної слід розглядати як візерунок.
Однак розщеплення (але не глобалізація) все ще відбувається неявно після заміни команд без котирувань (як у echo $(cmd)
).
Також іноді небажаним побічним ефектом не цитування змінної є видалення порожніх . zsh
Поведінка схоже на те , що ви можете досягти в інших оболонках, відключивши підстановку взагалі (з set -f
) і розщеплення (з IFS=''
). Все-таки в:
cmd $var
Там не буде ніякого розколу + Glob , але якщо $var
порожньо, замість того щоб отримувати один порожній аргумент, cmd
не отримають ніяких аргументів взагалі.
Це може спричинити помилки (як очевидні [ -n $var ]
). Це, можливо, може порушити очікування та припущення сценарію та спричинити вразливості, але я зараз не можу придумати не надто надуманий приклад).
А коли ви робите потрібен розкол + Glob оператор?
Так, це зазвичай, коли ви хочете залишити свою змінну без котирування. Але тоді вам потрібно переконатися, що ви правильно настроїли своїх розділених та глобальних операторів, перш ніж використовувати їх. Якщо ви хочете лише розділити частину, а не частину глобуса (що трапляється більшу частину часу), вам потрібно вимкнути глобулінг ( set -o noglob
/ set -f
) та виправити $IFS
. Інакше ви також спричините вразливості (як, наприклад, згаданий вище приклад CGI Девіда Корна).
Висновок
Коротше кажучи, залишити змінну (або заміну команд, або арифметичне розширення) цитатами в оболонках може бути дуже небезпечно, особливо якщо це зроблено в неправильних контекстах, і дуже важко зрозуміти, які це неправильні контексти.
Це одна з причин, чому це вважається поганою практикою .
Дякуємо за прочитане. Якщо це пройде над вашою головою, не хвилюйтесь. Не можна очікувати, що всі зрозуміють усі наслідки написання коду так, як вони його пишуть. Ось чому ми маємо
рекомендації щодо гарної практики , тому їх можна дотримуватися, не обов'язково розуміючи, чому.
(і якщо це ще не очевидно, будь ласка, уникайте написання коду, що захищається, в оболонки).
І будь ласка, цитуйте свої змінні у своїх відповідях на цьому сайті!
¹В ksh93
і pdksh
та похідних, розширення дужок також виконується, якщо не вимкнено ksh93
глобалізацію (у випадку версій до ksh93u +, навіть коли braceexpand
параметр вимкнено).