GNU/Linux >> Znalost Linux >  >> Linux

Bezpečnostní důsledky zapomenutí citovat proměnnou ve skořápkách Bash/posix?

Pokud už nějakou dobu sledujete unix.stackexchange.com,
byste už měli doufejme vědět, že ponechání proměnné
bez uvozovek v kontextu seznamu (jako v echo $var ) v Bourne/POSIX
shellech (zsh je výjimka) má velmi zvláštní význam a
by se nemělo dělat, pokud k tomu nemáte velmi dobrý důvod.

Obšírně se o tom diskutuje v řadě otázek a odpovědí zde (Příklady:Proč se můj skript shellu dusí mezerami nebo jinými speciálními znaky?, Kdy je nutné dvojité uvozování?, Rozšíření proměnné shellu a vliv glob a rozdělení na ni, Citace vs. expanze řetězce v uvozovkách )

Tak tomu bylo od prvního vydání Bourne
shellu na konci 70. let a nezměnilo to Korn shell
(jedna z největších
lítostí Davida Korna (otázka #7 )) nebo bash který většinou
kopíroval shell Korn, a tak to bylo specifikováno POSIX/Unix.

Nyní zde stále vidíme řadu odpovědí a dokonce
občas veřejně vydaný kód shellu, kde
proměnné nejsou citovány. Mysleli jste si, že se to teď lidé
naučí.

Podle mých zkušeností existují hlavně 3 typy lidí, kteří opomíjejí
citovat své proměnné:

  • začátečníky. To lze omluvit, protože se jedná o
    zcela neintuitivní syntaxi. A naší úlohou na tomto webu je
    je vzdělávat.

  • zapomnětliví lidé.

  • lidé, kteří nejsou přesvědčeni ani po opakovaném zatloukání,
    kteří si myslí, že autor Bourne shellu rozhodně neměl
    v úmyslu citovat všechny naše proměnné
    .

Možná je přesvědčíme, když odhalíme riziko spojené s
tímto druhem chování.

Co je nejhorší, co se může stát, pokud
zapomenete uvést své proměnné. Je to skutečně tak špatné?

O jakém druhu zranitelnosti zde mluvíme?

V jakých kontextech to může být problém?

Přijatá odpověď:

Preambule

Za prvé bych řekl, že to není správný způsob, jak problém vyřešit.
Je to trochu jako říkat „neměli byste zabíjet lidi, protože
jinak půjdete do vězení
“.

Podobně neuvádíte svou proměnnou, protože v opačném případě
zavádíte chyby zabezpečení. Uvádíte své
proměnné, protože to není správné (ale pokud vám může pomoci strach z vězení, proč ne).

Malé shrnutí pro ty, kteří právě naskočili do vlaku.

Ve většině shellů ponechání expanze proměnné bez uvozovek (ačkoli
to (a zbytek této odpovědi) platí také pro substituci příkazu
(`...` nebo $(...) ) a aritmetické rozšíření ($((...)) nebo $[...] )) má velmi zvláštní
význam. Nejlepší způsob, jak to popsat, je, že je to jako
vyvolání nějakého druhu implicitního split+glob operátor¹.

cmd $var

v jiném jazyce by bylo napsáno něco jako:

cmd(glob(split($var)))

$var se nejprve rozdělí na seznam slov podle složitých
pravidel zahrnujících $IFS speciální parametr (rozdělení část)
a každé slovo, které je výsledkem tohoto rozdělení, je považováno za
vzor který se rozbalí na seznam souborů, které mu odpovídají
(glob část).

Například $var obsahuje *.txt,/var/*.xml a $IFS obsahuje , , cmd bude voláno s řadou argumentů,
prvním je cmd a další jsou txt soubory v aktuálním adresáři a xml soubory v /var .

Pokud jste chtěli zavolat cmd pouze se dvěma doslovnými argumenty cmd a *.txt,/var/*.xml , napsali byste:

cmd "$var"

což by bylo ve vašem dalším známějším jazyce:

cmd($var)

Co rozumíme zranitelností v shellu ?

Koneckonců, od úsvitu času je známo, že skripty shell
by se neměly používat v kontextech citlivých na zabezpečení.
Jistě, OK, ponechání proměnné bez uvozovek je chyba, ale nemůže
nadělat tolik škody, že?

Navzdory skutečnosti, že by vám někdo řekl, že skripty shell
by se nikdy neměly používat pro webové CGI, nebo že naštěstí
většina systémů v dnešní době neumožňuje skripty setuid/setgid shell,
jeden věc, kterou shellshock (vzdáleně zneužitelná bash chyba
, která se objevila v titulcích v září 2014) odhalil, je, že
shelly jsou stále široce používány tam, kde by pravděpodobně neměly:
v CGI, v klientovi DHCP hook scripts, v příkazech sudoers,
vyvolané by (pokud ne jako ) příkazy setuid…

Někdy nevědomky. Například system('cmd $PATH_INFO') v php /perl /python Skript CGI vyvolá shell pro interpretaci tohoto příkazového řádku (nemluvě o
faktu, že cmd sám o sobě může být shell skript a jeho
autor možná nikdy nečekal, že bude volán z CGI).

Máte zranitelnost, když existuje cesta pro
eskalaci privilegií, tedy když někdo (říkejme mu útočník )
je schopen udělat něco, k čemu není určen.

Vždy to znamená útočník poskytování dat, tato data
zpracovává privilegovaný uživatel/proces, který neúmyslně
udělá něco, co by dělat neměl, ve většině případů kvůli
chybě.

V podstatě máte problém, když váš chybový kód zpracovává
data pod kontrolou útočníka .

Nyní není vždy zřejmé, kde jsou tato data může pocházet z
a často je těžké říct, zda se váš kód někdy dostane ke
zpracování nedůvěryhodných dat.

Co se proměnných týče, v případě CGI skriptu
je zcela zřejmé, že daty jsou parametry CGI GET/POST a
věci jako cookies, cesta, parametry hostitele…

U skriptu setuid (který běží jako jeden uživatel, když je vyvolán
jiným), jsou to argumenty nebo proměnné prostředí.

Dalším velmi častým vektorem jsou názvy souborů. Pokud získáváte
seznam souborů z adresáře, je možné, že do něj
vložil soubory útočník .

V tomto ohledu můžete být zranitelní i po výzvě interaktivního prostředí
(při zpracování souborů v /tmp nebo ~/tmp například).

Dokonce i ~/.bashrc může být zranitelný (například bash bude jej
interpretovat při vyvolání přes ssh spustit ForcedCommand jako v git nasazení serveru s některými proměnnými pod
kontrolou klienta).

Nyní skript nemůže být volán přímo ke zpracování nedůvěryhodných
dat, ale může být volán jiným příkazem, který ano. Nebo může být váš
nesprávný kód zkopírován a vložen do skriptů, které to dělají (vy za 3
roky nebo některý z vašich kolegů). Jedno místo, kde je to
obzvlášť kritické je v odpovědích na webech otázek a odpovědí, protože
nikdy nebudete vědět, kde mohou kopie vašeho kódu skončit.

Přístup k věci; jak je to špatné?

Ponechání proměnné (nebo substituce příkazu) bez uvozovek je zdaleka
zdrojem číslo jedna bezpečnostních chyb spojených
s kódem shellu. Částečně proto, že tyto chyby se často promítají do
zranitelností, ale také proto, že je tak běžné vidět neuvedené
proměnné.

Související:Proč používat install spíše než cp a mkdir?

Ve skutečnosti, když hledáte zranitelnosti v kódu shellu,
první věc, kterou musíte udělat, je hledat neuvedené proměnné. Je snadné
odhalit, často je to dobrý kandidát, obecně snadno dohledatelný zpět k
datům kontrolovaným útočníkem.

Existuje nekonečně mnoho způsobů, jak se může necitovaná proměnná změnit
ve zranitelnost. Uvedu zde jen několik společných trendů.

Zveřejňování informací

Většina lidí narazí na chyby spojené s neuvedenými
proměnnými kvůli rozdělení část (například je
běžné, že soubory mají dnes ve svých názvech mezery a mezera
je ve výchozí hodnotě IFS). Mnoho lidí přehlédne globus část. globus část je přinejmenším stejně nebezpečná jako rozdělení část.

Globování provedené na nevyčištěném externím vstupu znamená
útočníka
vám umožní číst obsah libovolného adresáře.

V:

echo You entered: $unsanitised_external_input

pokud $unsanitised_external_input obsahuje /* , to znamená
útočník
můžete vidět obsah / . Žádný velký problém. S /home/* to však bude
zajímavější který vám poskytne seznam
uživatelských jmen na počítači, /tmp/* , /home/*/.forward pro
rady k dalším nebezpečným praktikám, /etc/rc*/* pro povolené
služby… Není třeba je jednotlivě jmenovat. Hodnota /* /*/* /*/*/*... zobrazí pouze celý souborový systém.

Chyby zabezpečení způsobené odmítnutím služby.

Když vezmeme předchozí případ trochu příliš daleko, máme DoS.

Ve skutečnosti jakákoliv proměnná bez uvozovek v kontextu seznamu s neupraveným
vstupem je nejméně zranitelnost DoS.

Dokonce i zkušení skriptovači shellu běžně zapomínají citovat věci
jako:

#! /bin/sh -
: ${QUERYSTRING=$1}

: je příkaz no-op. Co by se mohlo pokazit?

To znamená přiřadit $1 na $QUERYSTRING pokud $QUERYSTRING byl odstaven. To je rychlý způsob, jak vytvořit skript CGI volatelný z
také z příkazového řádku.

Ten $QUERYSTRING je však stále rozšířen, a protože
není citován, split+glob je vyvolán operátor.

Nyní existují některé globusy, jejichž expanze je zvláště nákladná
. /*/*/*/* jeden je dost špatný, protože to znamená výpis
adresářů až o 4 úrovně níže. Kromě aktivity disku a CPU
to znamená ukládání desítek tisíc cest k souborům
(40 000 zde na minimálním virtuálním počítači serveru, z toho 10 000 adresářů).

Nyní /*/*/*/*/../../../../*/*/*/* znamená 40k x 10k a /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/* stačí na to
, aby srazil i ten nejmocnější stroj na kolena.

Vyzkoušejte to sami (i když buďte připraveni na to, že váš počítač
spadne nebo se zasekne):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Samozřejmě, pokud je kód:

echo $QUERYSTRING > /some/file

Poté můžete zaplnit disk.

Stačí vyhledat na Googlu shell
cgi nebo bash
cgi nebo ksh
cgi a najdete
několik stránek, které vám ukážou, jak psát CGI v shellu. Všimněte si
jak polovina z těch, kteří zpracovávají parametry, je zranitelná.

Dokonce i ten
vlastní
Davida Korna je zranitelný (podívejte se na zacházení se soubory cookie).

až po chyby zabezpečení při spuštění libovolného kódu

Libovolné spuštění kódu je nejhorším typem zranitelnosti,
protože útočník může spustit jakýkoli příkaz, neexistuje žádné omezení
co může dělat.

To je obecně rozdělení část, která k nim vede. Toto
rozdělení má za následek několik argumentů, které se předají příkazům
, když se očekává pouze jeden. Zatímco první z nich bude použit
v očekávaném kontextu, ostatní budou v jiném kontextu
, takže mohou být interpretovány odlišně. Lepší příklad:

awk -v foo=$external_input '$2 == foo'

Zde bylo záměrem přiřadit obsah $external_input shell proměnná na foo awk proměnná.

Nyní:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

Druhé slovo vyplývající z rozdělení $external_input není přiřazeno foo ale považováno za awk kód (zde
provede libovolný příkaz:uname ).

To je problém zejména u příkazů, které mohou provádět jiné
příkazy (awk , env , sed (GNU one), perl , find …) zvláště
s variantami GNU (které přijímají volby za argumenty).
Někdy byste neměli podezření, že příkazy mohou spouštět
jiné, jako je ksh , bash nebo zsh 's [ nebo printf

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Pokud vytvoříme adresář s názvem x -o yes , pak se test
stane pozitivním, protože je to úplně jiný
podmíněný výraz, který vyhodnocujeme.

Horší je, když vytvoříme soubor s názvem x -a a[0$(uname>&2)] -gt 1 ,
alespoň se všemi implementacemi ksh (což zahrnuje sh většiny komerčních Unices a některých BSD), který spouští uname protože tyto shelly provádějí aritmetické vyhodnocení na
operátorů numerického porovnání [ příkaz.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

Totéž s bash pro název souboru jako x -a -v a[0$(uname>&2)] .

Samozřejmě, pokud nemohou dosáhnout svévolného provedení, útočník může
se spokojit s menším poškozením (což může pomoci k libovolnému
provedení). Jakýkoli příkaz, který může zapisovat soubory nebo měnit
oprávnění, vlastnictví nebo mít hlavní či vedlejší efekt, by mohl být zneužit.

S názvy souborů lze dělat různé věci.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

A nakonec vytvoříte .. zapisovatelný (rekurzivně s GNU chmod ).

Skripty provádějící automatické zpracování souborů ve veřejně zapisovatelných oblastech, jako je /tmp je třeba psát velmi pečlivě.

A co [ $# -gt 1 ]

To je něco, co považuji za pobuřující. Někteří lidé si dají
problémy s otázkou, zda konkrétní rozšíření může být
problematické při rozhodování, zda mohou vynechat uvozovky.

Je to jako říct. Ahoj, vypadá to jako $# nemůže podléhat
operátoru split+glob, požádejme shell, aby to split+glob it
.
Nebo Hele, napíšeme nesprávný kód jen proto, že je
nepravděpodobné, že se chyba objeví
.

Jak je to nepravděpodobné? OK, $# (nebo $! , $? nebo jakákoli
aritmetická náhrada) může obsahovat pouze číslice (nebo - for
some²), takže globe část je venku. Pro rozdělení část udělat
něco, vše, co potřebujeme, je $IFS obsahovat číslice (nebo - ).

S některými shelly, $IFS může být zděděno z prostředí,
ale pokud prostředí není bezpečné, je stejně konec hry.

Související:Selhalo přesměrování na název globbed souboru?

Nyní, když napíšete funkci jako:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

To znamená, že chování vaší funkce závisí
na kontextu, ve kterém je volána. Nebo jinými slovy $IFS se stává jedním ze vstupů do něj. Přesně řečeno, když
píšete dokumentaci API pro vaši funkci, mělo by to být
něco jako:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

A kód volající vaši funkci musí zajistit $IFS neobsahuje
číslice. To vše proto, že se vám nechtělo psát
tyto 2 znaky ve dvojitých uvozovkách.

Nyní k tomu [ $# -eq 2 ] chyba, aby se stala zranitelností,
byste pro hodnotu $IFS nějak potřebovali dostat se pod
kontrolu útočníka . Je možné, že by se to normálně
nestalo, pokud by útočník nebyl podařilo zneužít další chybu.

To však není neslýchané. Běžným případem je, když lidé
zapomenou dezinfikovat data před jejich použitím v aritmetickém
výrazu. Již jsme viděli výše, že může umožnit
spuštění libovolného kódu v některých shellech, ale ve všech umožňuje útočníkovi přiřadit libovolné proměnné celočíselnou hodnotu.

Například:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

A s $1 s hodnotou (IFS=-1234567890) , že aritmetické
vyhodnocení má vedlejší účinek nastavení IFS a dalšího [ příkaz selže, což znamená, že se kontroluje příliš mnoho argumentů je
vynecháno.

A co když split+globe operátor není vyvolán?

Existuje další případ, kdy jsou potřeba uvozovky kolem proměnných a dalších rozšíření:když se to použije jako vzor.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

netestujte, zda $a a $b jsou stejné (kromě zsh ), ale pokud $a odpovídá vzoru v $b . A musíte uvést $b pokud chcete porovnávat jako řetězce (totéž v "${a#$b}" nebo "${a%$b}" nebo "${a##*$b*}" kde $b by měl být citován, pokud to nemá být považováno za vzor).

To znamená, že [[ $a = $b ]] může vrátit true v případech, kdy $a se liší od $b (například když $a je anything a $b je * ) nebo mohou vrátit false, pokud jsou totožné (například když oba $a a $b jsou [a] ).

Může to znamenat bezpečnostní chybu? Ano, jako každý bug. Tady útočník může změnit tok logického kódu vašeho skriptu a/nebo narušit předpoklady, které váš skript vytváří. Například s kódem jako:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

Útočník může obejít kontrolu předáním '[a]' '[a]' .

Nyní, pokud ani tento vzor, ​​ani split+glob Pokud použijete operátora, jaké je nebezpečí ponechání proměnné bez uvozovek?

Musím se přiznat, že píšu:

a=$b
case $a in...

Zde citace neškodí, ale nejsou nezbytně nutné.

Jedním z vedlejších efektů vynechání uvozovek v těchto případech (například v odpovědích na otázky a odpovědi) je však to, že může začátečníkům poslat špatnou zprávu:že může být v pořádku neuvádět proměnné .

Mohou si například začít myslet, že pokud a=$b je v pořádku, pak export a=$b bylo by také (což není v mnoha shellech, jako je to v argumentech pro export příkaz tak v kontextu seznamu) nebo env a=$b .

A co zsh ?

zsh opravil většinu těch designových nešikovností. V zsh (alespoň když není v režimu emulace sh/ksh), pokud chcete rozdělení nebo globbing nebo shoda vzorů , musíte o to výslovně požádat:$=var k rozdělení a $~var to glob nebo aby byl obsah proměnné považován za vzor.

Rozdělení (ale ne globování) se však stále provádí implicitně při nahrazení příkazu bez uvozovek (jako v echo $(cmd) ).

Někdy nechtěným vedlejším efektem neuvedení proměnné je také odstranění prázdných míst . zsh chování je podobné tomu, čeho můžete dosáhnout v jiných shellech úplným zakázáním globbingu (pomocí set -f ) a rozdělení (s IFS='' ). Přesto v:

cmd $var

Žádné split+glob nebudou , ale pokud $var je prázdný, namísto přijetí jednoho prázdného argumentu, cmd nedostane vůbec žádný argument.

To může způsobit chyby (jako je zřejmé [ -n $var ] ). To může možná narušit očekávání a předpoklady skriptu a způsobit zranitelnosti.

Protože prázdná proměnná může způsobit pouhé odstranění argumentu , to znamená, že další argument by mohl být interpretován ve špatném kontextu.

Jako příklad,

printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2

Pokud $attacker_supplied1 je prázdný, pak $attacker_supplied2 bude interpretováno jako aritmetický výraz (pro %d ) místo řetězce (pro %s ) a veškerá nehygienizovaná data použitá v aritmetickém výrazu jsou zranitelností vkládání příkazů do shellů podobných Korn, jako je zsh.

$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>

dobře, ale:

$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>

uname libovolný příkaz byl spuštěn.

A co když děláte potřebujete split+glob operátor?

Ano, to je obvykle, když chcete ponechat svou proměnnou bez uvozovek. Pak se ale musíte ujistit, že svůj rozdíl vyladíte a globe operátory správně před použitím. Pokud chcete pouze rozdělení část a ne globus část (což je většinou případ), pak musíte vypnout globbing (set -o noglob /set -f ) a opravte $IFS . Jinak způsobíte také zranitelnosti (jako výše zmíněný příklad CGI Davida Korna).

Závěr

Stručně řečeno, ponechání proměnné (nebo substituce příkazu nebo
aritmetické expanze) bez uvozovek v shellu může být velmi nebezpečné
zvláště když se to dělá ve špatných kontextech a je velmi
těžké zjistit, které jsou ty špatné kontexty.

To je jeden z důvodů, proč je to považováno za špatný postup .

Děkuji za přečtení až sem. Pokud vám to přeroste přes hlavu, nebojte se
. Nelze očekávat, že každý pochopí všechny důsledky
psaní kódu tak, jak jej píší. Proto máme doporučení osvědčených postupů , takže je lze sledovat, aniž by
nutně chápali proč.

Související:Jak vypočítat průměr hodnot ve sloupci s ohledem na informace z jiného sloupce?

(a v případě, že to ještě není zřejmé, vyvarujte se psaní
bezpečnostního citlivého kódu do shellů).

A uveďte prosím své proměnné ve svých odpovědích na tomto webu!


Linux
  1. [ :Neočekávaný operátor v programování shellu

  2. Alias ​​s proměnnou v bash

  3. Shell - Zápis proměnného obsahu do souboru

  1. Jak na to:Neomezená historie Bash/shell?

  2. Bezpečnostní důsledky použití relativních cest v proměnné prostředí PATH?

  3. bash - shoda proměnné bez ohledu na velikost písmen

  1. Přizpůsobení prostředí Bash

  2. Bash skriptování (I)

  3. Bash Dynamické (proměnné) názvy proměnných?