Я написав це як підручник в стилі підручника, який переглядав чудову відповідь Крісом Дауном вище.
У 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" ; }в ній.
Тепер, коли піддіаграма бачить звичайну змінну, яка починається з ()неї, інтерпретує решту як визначення функції. Але це лише тоді, коли починається нова оболонка. Як ми бачили вище, просто визначення звичайної змінної оболонки, починаючи з (), не призводить до того, що вона поводиться як функція. Ви повинні запустити передплату.
А тепер помилка "оболонки":
Як ми нещодавно бачили, коли нова оболонка заглиблює визначення регулярної змінної, починаючи з ()неї, інтерпретує її як функцію. Однак якщо після закриття дужки, що визначає функцію, надано більше, вона виконує все , що там є.
Це ще раз вимоги:
- Породжується новий баш
- Застосовується змінна середовище
- Ця змінна середовище починається з "()", а потім містить тіло функції всередині дужок, а потім має команди після цього
У цьому випадку вразливий баш виконує останні команди.
Приклад:
$ 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"