Podstawy skryptów shell #3
Napisał: Patryk Krawaczyński
29/07/2017 w Bezpieczeństwo Brak komentarzy. (artykuł nr 628, ilość słów: 685)
T
rzecia część [ 1 ] [ 2 ] będzie poruszać ataki wstrzykiwania do skryptów powłoki. Te typy ataków występują, gdy użytkownik dostarcza jako argumenty przechowywane w zmiennych skryptu spreparowane polecenia lub wartości zamiast oczekiwanych danych wejściowych. W dodatku użyte zmienne są pozbawione znaków cytowania, które służą do usuwania interpretacji znaków specjalnych przez powłokę. Na przykład:
#!/bin/bash read LOGIN read COMMAND if [ x$LOGIN = xroot ]; then echo $LOGIN eval $COMMAND fi
Skrypt ten posiada dwie luki w kodzie. Wykonajmy skrypt na początku w “normalnym” przebiegu tak, aby wykonała się część zawarta w warunku if
:
root@darkstar:~# bash -x ./example.sh + read LOGIN root + read COMMAND uptime + '[' xroot = xroot ']' + echo root root + eval uptime ++ uptime 21:47:19 up 104 days, 20:19, 1 user, load average: 0.10, 0.13, 0.10
Teraz zamiast wyrażenia “root” wprowadźmy niespodziewaną wartość do skryptu:
root@darkstar:~# bash -x ./example.sh + read LOGIN root = xroot -o x + read COMMAND uptime + '[' xroot = xroot -o x = xroot ']' + echo root = xroot -o x root = xroot -o x + eval uptime ++ uptime 22:26:27 up 104 days, 20:58, 1 user, load average: 0.10, 0.11, 0.09
Pomimo, że wartość LOGIN nie jest równa wyrażeniu xroot – jak zakładaliśmy – to instrukcje wewnątrz warunku if
zostały wykonane mimo to, co jest nieoczekiwanym dla nas zachowaniem. W celu naprawy tego błędu warunek if
powinien mieć postać:
if [ "x$LOGIN" = "xroot" ] ; then
Drugim błędem jest oczywiście pozwolenie na uruchomienie funkcji eval na danych przekazanych przez użytkownika. Do tego nigdy nie powinniśmy dopuścić chyba, że bardzo starannie przeprowadzamy sanityzację danych wejściowych i używamy białej listy, aby ograniczyć dozwolone wartości. Gdyby powyższy skrypt był uruchamiany z prawami administratora, atakujący bez problemu byłby w stanie przeprowadzić eskalację swoich uprawnień. Spójrzmy na drugi przykład, który nie używa funkcji eval, ale zapisuje dane do pliku bez ochrony ich wartości:
#!/bin/bash read DATA echo ls $DATA >> showdata.sh chmod a+x showdata.sh ./showdata.sh
Jeśli użytkownik zdecyduje się na przekazanie do zmiennej DATA polecenia: ; rm /etc/shadow, spowoduje to usunięcie tego pliku:
root@darkstar:~# bash -x test.sh + read DATA ; rm /etc/shadow + echo ls ';' rm /etc/shadow + chmod a+x showdata.sh + ./showdata.sh root@darkstar:~# cat showdata.sh ls ; rm /etc/shadow
Spróbujmy to naprawić poprzez podmianę linii, która opakuje nam dane wejściowe w cudzysłów:
echo ls "\"$DATA\"" >> showdata.sh export DATA
Rzeczywiście uruchamiając ponownie skrypt z tym samym poleceniem jesteśmy chronieni:
root@darkstar:~# bash -x test.sh + read DATA ; rm /etc/shadow + echo ls '"; rm dane"' + export DATA + chmod a+x showdata.sh + ./showdata.sh ls: cannot access ; rm /etc/shadow: No such file or directory root@darkstar:~# cat showdata.sh ls "; rm /etc/shadow"
Ale w ten sposób nieświadomie otworzyliśmy sobie drugą lukę bezpieczeństwa. Wystarczy teraz wprowadzić wartość, która będzie już opakowana w cudzysłów:
root@darkstar:~# bash -x test.sh + read DATA "; rm /etc/shadow; echo " + echo ls '""; rm /etc/shadow; echo ""' + export DATA + chmod a+x showdata.sh + ./showdata.sh ls: cannot access : No such file or directory root@darkstar:~# cat showdata.sh ls ""; rm /etc/shadow; echo ""
Możemy naprawić ten błąd na dwa sposoby. Pierwszy z nich wymaga obecności funkcji export
, która podniesie nam zmienną DATA
do rangi globalnej, aby była widoczna dla skryptu showdata.sh
– w przeciwnym wypadku będzie to dla niego tylko niezdefiniowana zmienna. Podmieniamy linię zaczynającą się od echo
na:
echo ls "\"\$DATA\"" >> showdata.sh
i możemy przetestować skrypt dla dwóch poprzednich, wrogich wartości. Drugi sposób angażuje, jako filtr dla cudzysłowów narzędzie sed, ale nie wymaga zmiennych globalnych:
FILTER="$(echo "$DATA" | sed "s/'/'\"'\"'/g")" echo ls "'$FILTER'" >> showdata.sh
Testy tego rozwiązania dają następujące wyniki:
root@darkstar:~# bash -x test.sh + read DATA ; rm /etc/shadow ++ echo '; rm /etc/shadow' ++ sed 's/'\''/'\''"'\''"'\''/g' + FILTER='; rm /etc/shadow' + echo ls ''\''; rm /etc/shadow'\''' + chmod a+x showdata.sh + ./showdata.sh ls: cannot access ; rm /etc/shadow: No such file or directory root@darkstar:~# cat showdata.sh ls '; rm /etc/shadow' ----- root@darkstar:~# bash -x test.sh + read DATA "; rm /etc/shadow; echo " ++ echo '"; rm /etc/shadow; echo "' ++ sed 's/'\''/'\''"'\''"'\''/g' + FILTER='"; rm /etc/shadow; echo "' + echo ls ''\''"; rm /etc/shadow; echo "'\''' + chmod a+x showdata.sh + ./showdata.sh ls: cannot access "; rm /etc/shadow; echo ": No such file or directory root@darkstar:~# cat showdata.sh ls '"; rm /etc/shadow; echo "'
Polecam również testy tego filtra z bardziej rozbudowanymi ciągami.
Więcej informacji: Quotes and escaping, How to escape single quotes within single quoted strings?