Що означає env x = '() {:;}; команда 'bash do, і чому це небезпечно?


237

Очевидно, є вразливість (CVE-2014-6271) в bash: Bash, спеціально створені змінні середовища, атака введення коду

Я намагаюся з’ясувати, що відбувається, але я не зовсім впевнений, що це розумію. Як може echoбути виконаний таким, яким він є в одиничних лапках?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

EDIT 1 : Виправлена ​​система виглядає так:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

EDIT 2 : Існує відповідна вразливість / виправлення: CVE-2014-7169, який використовує дещо інший тест:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

незавершений вихід :

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

частково (рання версія) виправлений вихід :

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

виправлене виведення до CVE-2014-7169 і включаючи:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

EDIT 3 : історія продовжується:


Це не відлуння, яке виконується. його визначення функції x. Якщо функція, визначена в x, виконує деяку підступну роботу, немає можливості bash перевірити значення повернення, якщо функція x була реальною. Зверніть увагу, що функція порожня в тестовому коді. Неперевірене значення повернення може призвести до введення сценарію. Введення сценарію призводить до ескалації привілеїв, а ескалація привілеїв призводить до кореневого доступу. Патч вимикає створення x як функції
eyoung100

26
eyoung100, відлуння не виконується. Ви можете бачити, що воно виконується, оскільки слово vulnerableз'являється у висновку. Основна проблема полягає в тому, що bash аналізує і виконує код також після визначення функції. Дивіться /bin/idчастину seclists.org/oss-sec/2014/q3/650 для іншого прикладу.
Мікель

4
Просто швидкий побічний коментар. Компанія Red Hat порадила, що випущений патч є лише частковим патчем і залишає системи все ще в небезпеці.
Петро,

2
@ eyoung100 різниця полягає в тому, що код всередині функції виконується лише тоді, коли явно викликається змінна середовища. Код після визначення функції виконується щоразу, коли починається новий процес Bash.
Девід Фаррелл

1
Дивіться stackoverflow.com/questions/26022248/… для додаткових відомостей
Barmar

Відповіді:


204

bash магазини експортували визначення функцій як змінні середовища. Експортовані функції виглядають приблизно так:

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

Тобто змінна середовище fooмає буквальний зміст:

() {  bar
}

Коли запускається новий екземпляр bash, він шукає ці спеціально створені змінні середовища та інтерпретує їх як визначення функцій. Ви можете навіть написати сам і побачити, що він все ще працює:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

На жаль, розбір визначень функцій з рядків (змінних оточення) може мати ширші ефекти, ніж передбачалося. У непакетованих версіях він також інтерпретує довільні команди, які виникають після закінчення визначення функції. Це пов'язано з недостатніми обмеженнями у визначенні прийнятних функціональних рядків у середовищі. Наприклад:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

Зауважте, що відлуння за межами визначення функції було несподівано виконано під час запуску bash. Визначення функції - це лише крок для того, щоб отримати оцінку та скористатися експлуатуванням, саме визначення функції та використана змінна середовища є довільними. Оболонка розглядає змінні середовища, бачить foo, схоже, що відповідає тим обмеженням, які вона знає про те, як виглядає визначення функції, і вона оцінює рядок, ненавмисно виконуючи також ехо (що може бути будь-якою командою, шкідливою чи ні).

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

Ось приклад життєздатної атаки. Ви запускаєте веб-сервер, який десь протягом свого часу працює з уразливою оболонкою. Цей веб-сервер передає змінні середовища в сценарій bash, наприклад, якщо ви використовуєте CGI, інформація про HTTP-запит часто включається як змінні середовища з веб-сервера. Наприклад, HTTP_USER_AGENTможе бути встановлено вміст вашого агента користувача. Це означає, що якщо ви підробляєте свій користувальницький агент на зразок '() {:; }; echo foo ', коли цей сценарій оболонки echo fooбуде запущений, буде виконано. Знову ж таки, echo fooможе бути будь-що, шкідливе чи ні.


3
Чи може це вплинути на будь-яку іншу оболонку, схожу на Баша, як Zsh?
Амеліо Васкес-Рейна

3
@ user815423426 Ні, zsh не має цієї функції. У Ksh це є, але реалізовано по-іншому, я думаю, що функції можна передавати лише в дуже вузьких обставинах, тільки якщо оболонка розвивається, а не через навколишнє середовище.
Жиль

20
@ user815423426 rc - це інша оболонка, яка передає функції в середовищі, але її зі змінною з іменами з префіксом "fn_" і вони інтерпретуються лише при виклику.
Стефан Шазелас

18
@ StéphaneChazelas - дякую за повідомлення про помилку.
Мисливець на оленів

13
@gnclmorais Ти маєш на увазі, що ти працюєш, export bar='() { echo "bar" ; }'; zsh -c barа він відображається, barа не zsh:1: command not found: bar? Ви впевнені, що не плутаєте оболонку, яку ви викликаєте, з оболонкою, яку ви використовуєте для настройки тесту?
Жиль

85

Це може допомогти надалі продемонструвати, що відбувається:

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

Якщо ви використовуєте вразливу оболонку, то, коли ви запускаєте нову підзарядку (тут просто за допомогою оператора bash), ви побачите, що довільний код ( echo "pwned") негайно виконується як частина його ініціації. Мабуть, оболонка бачить, що змінна середовища (манекен) містить визначення функції та оцінює визначення для того, щоб визначити цю функцію у своєму оточенні (зауважте, що вона не виконує функцію: вона надрукувала б "привіт".)

На жаль, він не просто оцінює визначення функції, він оцінює весь текст значення змінної середовища, включаючи, можливо, зловмисні заяви (и), які відповідають визначенню функції. Зауважте, що без початкового визначення функції змінна середовища не була б оцінена, вона буде просто додана до середовища як текстовий рядок. Як зазначив Кріс Даун, це специфічний механізм для імпорту функцій експортованих оболонок.

Ми можемо бачити функцію, яка була визначена в новій оболонці (і що вона позначена як експортована туди), і ми можемо її виконати. Крім того, манекен не імпортовано як текстову змінну:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

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


17
Хоча прийнята відповідь насправді говорить це, якщо ви її уважно прочитаєте, я вважаю цю відповідь ще більш зрозумілою та корисною для розуміння того, що саме проблема визначення (а не виконання самої функції) є проблемою.
natevw

1
чому цей приклад має exportкоманду, а інші env? Я думав env, що використовується, щоб визначити змінні середовища, які будуть викликатися, коли запускається інша оболонка bash. то як це працює зexport
Харіс,

До цього моменту не було прийнято відповіді. Я, мабуть, зачекаю ще пару днів, перш ніж приймати його. Недоліком цієї відповіді є те, що вона не розбиває початкову команду, а також не обговорює, як дістатись від вихідної команди у питанні до команд у цій відповіді, показуючи, що вони однакові. Крім того, це хороше пояснення.
jippie

@ralph - envі exportвизначення, і експортне середовище, щоб вони були доступні в підпакеті. Проблема полягає фактично в тому, як ці експортовані визначення імпортуються в середовище підшкільної оболонки, а саме в механізмі, який імпортує функціональні визначення.
sdenham

1
@ralph - envзапускає команду із встановленими деякими параметрами та змінними середовища. Зауважте, що в оригінальних прикладах запитань envвстановлюється xрядок і викликає bash -cкоманду для запуску. Якщо ви зробите це env x='foo' vim, Vim запуститься, і там ви можете зателефонувати до його містить оболонку / середовище з !echo $x, і він буде друкувати foo, але якщо ви вийдете і echo $x, це не буде визначено, оскільки воно існувало лише під час запуску vim за допомогою envкоманди. exportКоманда замість встановлює постійні значення в поточній середовищі , так що подоболочка запустити пізніше буде використовувати їх.
Гері Фікслер

72

Я написав це як підручник в стилі підручника, який переглядав чудову відповідь Крісом Дауном вище.


У bash ви можете мати такі змінні оболонки

$ t="hi there"
$ echo $t
hi there
$

За замовчуванням ці змінні не успадковуються дочірніми процесами.

$ bash
$ echo $t

$ exit

Але якщо ви позначите їх для експорту, bash встановить прапор, що означає, що вони потраплять у середовище підпроцесів (хоча envpпараметр мало що видно, mainу вашій програмі C є три параметри: main(int argc, char *argv[], char *envp[])де останній масив покажчиків - це масив змінних оболонок з їх визначеннями).

Тож давайте експортуємо tтак:

$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit

Тоді як вище tбуло визначено в нижній частині корпусу, воно з’являється після того, як ми експортували його (використовуйте, export -n tякщо ви хочете припинити його експорт).

Але функції в баші - це інша тварина. Ви заявляєте їх так:

$ fn() { echo "test"; }

А тепер ви можете просто викликати функцію, викликаючи її так, ніби це була інша команда оболонки:

$ fn
test
$

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

$ bash
$ fn
fn: command not found
$ exit

Ми можемо експортувати функцію за допомогою export -f:

$ export -f fn
$ bash
$ fn
test
$ exit

Ось складна частина: експортована функція на зразок fnперетворюється на змінну середовища так, як tвище був експорт змінної оболонки . Це не відбувається, коли fnбула локальною змінною, але після експорту ми можемо бачити її як змінну оболонки. Однак ви також можете мати звичайну змінну оболонки (тобто без функції) з тим самим іменем. bash розрізняє на основі вмісту змінної:

$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 

Тепер ми можемо використовувати envдля показу всіх змінних оболонок, позначених для експорту, і звичайних, fnі функцій fn:

$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$

Під-оболонці буде передаватися обидва визначення: одне як звичайна змінна і одне як функція:

$ bash
$ echo $fn
regular
$ fn
test
$ exit

Ви можете визначити, fnяк ми це робили вище, або безпосередньо як звичайне призначення змінної:

$ fn='() { echo "direct" ; }'

Зауважте, це надзвичайно незвичайна річ! Зазвичай ми б визначали функцію, fnяк це було зроблено вище із fn() {...}синтаксисом. Але оскільки Bash експортує його через навколишнє середовище, ми можемо «скоротити» безпосередньо до вищезгаданого регулярного визначення. Зауважте, що (можливо, проти вашої інтуїції) це не призводить до появи нової функції, fnнаявної в поточній оболонці. Але якщо ви породжуєте ** під ** оболонку, то вона буде.

Скасуємо експорт функції fnта залишимо новий звичайний fn(як показано вище) недоторканим.

$ export -nf fn

Тепер функція fnбільше не експортується, але звичайна змінна fnє, і вона міститься () { echo "direct" ; }в ній.

Тепер, коли піддіаграма бачить звичайну змінну, яка починається з ()неї, інтерпретує решту як визначення функції. Але це лише тоді, коли починається нова оболонка. Як ми бачили вище, просто визначення звичайної змінної оболонки, починаючи з (), не призводить до того, що вона поводиться як функція. Ви повинні запустити передплату.

А тепер помилка "оболонки":

Як ми нещодавно бачили, коли нова оболонка заглиблює визначення регулярної змінної, починаючи з ()неї, інтерпретує її як функцію. Однак якщо після закриття дужки, що визначає функцію, надано більше, вона виконує все , що там є.

Це ще раз вимоги:

  1. Породжується новий баш
  2. Застосовується змінна середовище
  3. Ця змінна середовище починається з "()", а потім містить тіло функції всередині дужок, а потім має команди після цього

У цьому випадку вразливий баш виконує останні команди.

Приклад:

$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$

Звичайна експортована змінна exбула передана до підклітини, яка була інтерпретована як функція, exале трейлінг команди виконувались ( this is bad), коли покоління породжувалося.


Пояснення витонченого однорядкового тесту

Популярна однолінійка для тестування на вразливість Shellshock - це та, що цитується у запитанні @ jippie:

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Ось розбивка: спочатку :в bash - це лише скорочення true. trueі :обидва оцінюють (ви здогадалися) правдою, в баш:

$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$

По-друге, envкоманда (також вбудована в bash) друкує змінні середовища (як ми бачили вище), але також може бути використана для запуску однієї команди з експортованою змінною (або змінними), наданої цій команді, і bash -cвиконує одну команду зі своєї командний рядок:

$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$

Отже, зшивши всі ці речі разом, ми можемо запустити bash як команду, дати йому якусь фіктивну річ (на зразок bash -c echo this is a test) та експортувати змінну, яка починається з ()того, що нижня частина буде інтерпретувати її як функцію. Якщо оболонка присутня, вона також негайно виконує будь-які трейлінг-команди в нижній частині. Оскільки функція, яку ми передаємо, не має значення для нас (але повинна розбиратися!), Ми використовуємо найкоротший дозволений функцію, що можна уявити:

$ f() { :;}
$ f
$ 

Тут функція fпросто виконує :команду, яка повертає true та виходить. Тепер додайте до цього якусь "злу" команду та експортуйте звичайну змінну в підшару, і ви виграєте. Ось знову один лайнер:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Так xекспортується у вигляді звичайної змінної з простою дійсною функцією з echo vulnerableприкріпленою до кінця. Це передається bash, і bash інтерпретується xяк функція (яка нас не хвилює), то, можливо, виконує echo vulnerableif присутнім оболонки.

Ми можемо трохи скоротити однолінійку, видаливши this is a testповідомлення:

$ env x='() { :;}; echo vulnerable' bash -c :

Це не заважає, this is a testале запускає тиху :команду ще раз. (Якщо ви відмовитеся від цього, -c :тоді ви сідаєте в передплату і виходите вручну.) Мабуть, найбільш зручною для користувача версія була б ця:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"

12
Приємне пояснення. Це питання отримує багато поглядів (напевно, не всі так добре знають баш як інші), і я вважаю, що ще ніхто не витратив пару слів на те, що { :;};насправді йдеться. Це було б приємним доповненням до вашої відповіді, на мою думку. Чи можете пояснити, як ви переходите зі свого прикладу до оригінальної команди у питанні?
jippie

20

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

Однак CVE-2014-6271 відрізняється.

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

Приклад, який було висунуто в контексті CVE-2014-6271, - це сценарії, що використовуються для розбору журналів. Можливо, у них є цілком законна потреба в передачі неперевірених даних навколо змінних середовища. Звичайно, назва такої змінної середовища вибирається таким, що не має негативних наслідків.

Але ось, що погано в цій конкретної вразливості баша. Його можна використовувати через будь-яку назву змінної. Якщо ви створюєте змінну середовища, яка називається GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, ви не очікуєте, що будь-яка інша програма, окрім власного сценарію, інтерпретує вміст цієї змінної середовища. Але, використовуючи цю помилку bash, кожна змінна середовища стає вектором атаки.

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

Якщо program1дзвінки, program2які в свою чергу дзвонять program3, то вони program1можуть передавати дані program3через змінні середовища. Кожна програма має певний перелік змінних оточуючих середовищ, які вона встановлює, та певний список, на який вона діє. Якщо ви вибрали ім'я, яке не було розпізнане program2, ви можете передавати дані program1до, program3не турбуючись про те, що це може негативно вплинути program2.

Зловмисник, знаючи точні назви змінних, що експортуються, program1та імена змінних, інтерпретованих program2не може використовувати ці знання для зміни поведінки 'program2', якщо між набором імен немає перекриття.

Але це program2вийшло з ладу, якщо був bashсценарій, тому що завдяки цій поминці bashінтерпретувати кожну змінну середовища як код.


1
"кожна змінна середовища стає вектором атаки" - це та частина, яку мені не вистачало. Дякую.
wrschneider

9

Це пояснено у статті, яку ви пов’язали ...

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

Що означає, що bash, який викликається, -c "echo this is a test"виконує код у єдиних лапках при його виклику.

Bash має функції, хоча і в дещо обмеженій реалізації, і ці функції bash можна помістити в змінні середовища. Цей недолік спрацьовує, коли в кінець цих визначень функцій додається додатковий код (всередині змінної enivronment).

Означає приклад коду, який ви опублікували, використовує той факт, що викликаний bash не перестає оцінювати цей рядок після виконання завдання. Призначення функції в цьому випадку.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.