Präambel
Zunächst würde ich sagen, dass dies nicht der richtige Weg ist, um das Problem anzugehen. Es ist ein bisschen so, als würde man sagen " Du sollst keine Menschen ermorden, weil du sonst ins Gefängnis kommst ".
Ebenso zitieren Sie Ihre Variable nicht, weil Sie sonst Sicherheitslücken einführen. Sie zitieren Ihre Variablen, weil es falsch ist, dies nicht zu tun (aber wenn die Angst vor dem Gefängnis helfen kann, warum nicht).
Eine kleine Zusammenfassung für diejenigen, die gerade in den Zug gesprungen sind.
In den meisten Shells hat das Nichtanführen einer variablen Erweiterung (obwohl dies (und der Rest dieser Antwort) auch für die Befehlssubstitution ( `...`
oder $(...)
) und die arithmetische Erweiterung ( $((...))
oder $[...]
) gilt) eine ganz besondere Bedeutung. Am besten lässt sich das so beschreiben, als würde man eine Art impliziten split + glob- Operator1 aufrufen .
cmd $var
in einer anderen Sprache würde etwa geschrieben werden:
cmd(glob(split($var)))
$var
wird zuerst in eine Liste von Wörtern nach komplexen Regeln aufgeteilt, die den $IFS
speziellen Parameter (den geteilten Teil) betreffen, und dann wird jedes Wort, das aus dieser Aufteilung resultiert, als ein Muster betrachtet, das zu einer Liste von Dateien erweitert wird, die mit ihm übereinstimmen (der globale Teil). .
Wenn beispielsweise $var
enthält *.txt,/var/*.xml
und $IFS
enthält ,
, wird cmd
dies mit einer Reihe von Argumenten aufgerufen, wobei das erste cmd
und das nächste die txt
Dateien im aktuellen Verzeichnis und die xml
Dateien in sind /var
.
Wenn Sie nur cmd
mit den beiden wörtlichen Argumenten cmd
und aufrufen möchten *.txt,/var/*.xml
, würden Sie schreiben:
cmd "$var"
was in deiner anderen, bekannteren Sprache wäre:
cmd($var)
Was verstehen wir unter Schwachstelle in einer Shell ?
Schließlich ist seit jeher bekannt, dass Shell-Skripte nicht in sicherheitsrelevanten Kontexten verwendet werden sollten. Sicher, OK, eine Variable ohne Anführungszeichen zu lassen, ist ein Fehler, aber das kann nicht so viel Schaden anrichten, oder?
Nun, trotz der Tatsache, dass irgendjemand Ihnen sagen würde, dass Shellskripte niemals für Web - CGIs verwendet werden sollten, oder dass die meisten Systeme heutzutage zum Glück keine setuid / setgid - Shellskripte zulassen, eine Sache, die Shellshock (der aus der Ferne ausnutzbare Bash - Bug, der den Fehler verursachte) Schlagzeilen im September 2014) haben ergeben, dass Shells immer noch häufig dort verwendet werden, wo sie wahrscheinlich nicht verwendet werden sollten: in CGIs, in DHCP-Client-Hook-Skripten, in sudoers-Befehlen, die von (wenn nicht als ) setuid-Befehlen aufgerufen werden ...
Manchmal unwissentlich. Beispielsweise ruft system('cmd $PATH_INFO')
ein php
/ perl
/ python
CGI-Skript eine Shell auf, um diese Befehlszeile zu interpretieren (ganz zu schweigen von der Tatsache, dass es cmd
sich möglicherweise um ein Shell-Skript handelt und der Autor möglicherweise nie damit gerechnet hat, dass es von einem CGI aufgerufen wird).
Sie haben eine Sicherheitslücke, wenn es einen Pfad für die Eskalation von Berechtigungen gibt, dh wenn jemand (nennen wir ihn den Angreifer ) in der Lage ist, etwas zu tun, für das er nicht vorgesehen ist.
Dies bedeutet immer, dass der Angreifer Daten bereitstellt, die von einem privilegierten Benutzer / Prozess verarbeitet werden, der versehentlich etwas tut, was er nicht tun sollte, in den meisten Fällen aufgrund eines Fehlers.
Grundsätzlich haben Sie ein Problem, wenn Ihr Buggy-Code Daten verarbeitet, die vom Angreifer kontrolliert werden .
Nun ist es nicht immer klar, woher diese Daten stammen, und es ist oft schwer zu sagen, ob Ihr Code jemals nicht vertrauenswürdige Daten verarbeiten wird.
In Bezug auf Variablen ist es ziemlich offensichtlich, dass es sich bei den Daten bei einem CGI-Skript um die CGI-GET / POST-Parameter handelt und um Dinge wie Cookies, Pfad, Host ... -Parameter.
Bei einem setuid-Skript (das bei Aufruf durch einen anderen Benutzer als ein Benutzer ausgeführt wird) handelt es sich um die Argumente oder Umgebungsvariablen.
Ein weiterer sehr häufiger Vektor sind Dateinamen. Wenn Sie eine Dateiliste aus einem Verzeichnis erhalten, wurden möglicherweise Dateien vom Angreifer dort abgelegt .
In dieser Hinsicht können Sie selbst auf Aufforderung einer interaktiven Shell verwundbar sein (
z. B. beim Verarbeiten von Dateien in /tmp
oder ~/tmp
).
Sogar ein ~/.bashrc
kann verwundbar sein (zum Beispiel bash
wird es interpretiert, wenn es aufgerufen wird ssh
, um ForcedCommand
in git
Serverbereitstellungen mit einigen Variablen unter der Kontrolle des Clients ein ähnliches Verhalten auszuführen ).
Nun kann ein Skript möglicherweise nicht direkt aufgerufen werden, um nicht vertrauenswürdige Daten zu verarbeiten, aber es kann von einem anderen Befehl aufgerufen werden, der dies tut. Oder Ihr falscher Code wird möglicherweise in Skripten eingefügt, die dies tun (von Ihnen oder einem Ihrer Kollegen nach drei Jahren). Ein besonders kritischer Punkt ist die Beantwortung von Fragen und Antworten, da Sie nie wissen, wo möglicherweise Kopien Ihres Codes landen.
Runter zur Sache; wie schlimm ist es?
Eine Variable (oder eine Befehlsersetzung) nicht in Anführungszeichen zu setzen, ist bei weitem die häufigste Ursache für Sicherheitslücken im Zusammenhang mit Shell-Code. Zum Teil, weil diese Fehler häufig zu Sicherheitslücken führen, aber auch, weil es so häufig vorkommt, dass Variablen nicht in Anführungszeichen gesetzt werden.
Wenn Sie nach Schwachstellen im Shell-Code suchen, müssen Sie zunächst nach Variablen suchen, die nicht in Anführungszeichen stehen. Es ist leicht zu erkennen, oftmals ein guter Kandidat, und es ist im Allgemeinen einfach, auf angreifergesteuerte Daten zurückzugreifen.
Es gibt unendlich viele Möglichkeiten, wie eine Variable ohne Anführungszeichen zu einer Sicherheitsanfälligkeit werden kann. Ich werde hier nur einige allgemeine Trends nennen.
Offenlegung von Informationen
Die meisten Leute werden aufgrund des geteilten Teils auf Fehler stoßen, die mit nicht zitierten Variablen zusammenhängen (zum Beispiel ist es heutzutage üblich, dass Dateien Leerzeichen in ihren Namen haben und Leerzeichen im Standardwert von IFS sind). Viele Leute werden den Glob- Teil übersehen
. Der Glob- Teil ist mindestens so gefährlich wie der
Split- Teil.
Wenn der Angreifer nach einer nicht bereinigten externen Eingabe eine Globulation durchführt, kann er Sie dazu bringen, den Inhalt eines beliebigen Verzeichnisses zu lesen.
Im:
echo You entered: $unsanitised_external_input
Wenn $unsanitised_external_input
enthält /*
, bedeutet dies, dass der Angreifer den Inhalt von sehen kann /
. Keine große Sache. Es wird interessanter , wenn auch mit /home/*
denen gibt Ihnen eine Liste von Benutzernamen auf der Maschine, /tmp/*
, /home/*/.forward
für Hinweise auf andere gefährliche Praktiken, /etc/rc*/*
für aktivierte Dienste ... Keine Notwendigkeit , sie einzeln zu nennen. Ein Wert von /*
/*/* /*/*/*...
listet nur das gesamte Dateisystem auf.
Denial-of-Service-Schwachstellen.
Nehmen wir den vorherigen Fall etwas zu weit und wir haben ein DoS.
Tatsächlich ist jede nicht in Anführungszeichen gesetzte Variable im Listenkontext mit nicht bereinigter Eingabe mindestens eine DoS-Sicherheitsanfälligkeit.
Sogar erfahrene Shell-Skripter vergessen häufig, Dinge zu zitieren wie:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
ist der no-op Befehl. Was könnte möglicherweise falsch laufen?
Das soll zuweisen $1
, $QUERYSTRING
wenn nicht gesetzt $QUERYSTRING
war. Dies ist eine schnelle Möglichkeit, ein CGI-Skript auch über die Befehlszeile aufrufbar zu machen.
Das $QUERYSTRING
ist jedoch immer noch erweitert und da es nicht in Anführungszeichen steht, wird der split + glob- Operator aufgerufen.
Nun gibt es einige Globs, deren Erweiterung besonders teuer ist. Das /*/*/*/*
ist schon schlimm genug, da es bedeutet, dass Verzeichnisse bis zu 4 Ebenen tiefer aufgelistet werden. Zusätzlich zur Festplatten- und CPU-Aktivität bedeutet dies das Speichern von Zehntausenden von Dateipfaden (40 KB hier auf einer minimalen Server-VM, 10 KB davon Verzeichnisse).
Jetzt /*/*/*/*/../../../../*/*/*/*
heißt es 40k x 10k und
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
reicht aus, um selbst die mächtigste Maschine in die Knie zu zwingen.
Probieren Sie es selbst aus (seien Sie jedoch darauf vorbereitet, dass Ihre Maschine abstürzt oder hängen bleibt):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Natürlich, wenn der Code ist:
echo $QUERYSTRING > /some/file
Dann können Sie die Festplatte füllen.
Führen Sie einfach eine Google-Suche in Shell-CGI oder Bash-CGI oder Ksh-CGI durch , und Sie werden einige Seiten finden, die Ihnen zeigen, wie Sie CGIs in Shells schreiben. Beachten Sie, wie die Hälfte dieser Prozessparameter anfällig ist.
Sogar der von David Korn
ist verwundbar (siehe Cookie-Behandlung).
bis zu beliebigen Sicherheitslücken bei der Codeausführung
Die Ausführung von willkürlichem Code ist die schwerwiegendste Sicherheitsanfälligkeit, da der Angreifer unbegrenzte Möglichkeiten hat, Befehle auszuführen.
Das ist im Allgemeinen der geteilte Teil, der zu diesen führt. Diese Aufteilung führt dazu, dass mehrere Argumente an Befehle übergeben werden, wenn nur eines erwartet wird. Während die erste davon im erwarteten Kontext verwendet wird, werden die anderen in einem anderen Kontext verwendet, sodass sie möglicherweise unterschiedlich interpretiert werden. Besser mit einem Beispiel:
awk -v foo=$external_input '$2 == foo'
Hier sollte der Inhalt der $external_input
Shell-Variablen der foo
awk
Variablen zugewiesen
werden.
Jetzt:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Das zweite Wort, das sich aus der Aufteilung von $external_input
ergibt, wird nicht zugewiesen, foo
sondern als awk
Code betrachtet (hier wird ein beliebiger Befehl ausgeführt:) uname
.
Das ist vor allem ein Problem für Befehle , die anderen Befehle ausführen kann ( awk
, env
, sed
(GNU eins) perl
, find
...) vor allem mit den GNU - Varianten (die Optionen , die nach Argumenten akzeptieren). Manchmal würde man keine Befehle vermuten, um andere ausführen zu können wie ksh
, bash
oder zsh
's [
oder printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Wenn wir ein Verzeichnis namens erstellen x -o yes
, wird der Test positiv, da es sich um einen völlig anderen bedingten Ausdruck handelt, den wir auswerten.
Schlimmer noch, wenn wir eine Datei namens erstellen x -a a[0$(uname>&2)] -gt 1
, die mindestens mit allen ksh-Implementierungen (einschließlich der sh
meisten kommerziellen Unices und einiger BSDs) ausgeführt wird, uname
weil diese Shells arithmetische Auswertungen für die numerischen Vergleichsoperatoren des [
Befehls durchführen.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
Gleiches gilt bash
für einen Dateinamen wie x -a -v a[0$(uname>&2)]
.
Wenn sie nicht willkürlich hingerichtet werden können, kann sich der Angreifer natürlich mit weniger Schaden zufrieden geben (was helfen kann, willkürlich hingerichtet zu werden). Jeder Befehl, der Dateien schreiben oder Berechtigungen, Besitz oder Nebeneffekte ändern kann, kann ausgenutzt werden.
Mit Dateinamen können alle möglichen Dinge erledigt werden.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
Und am Ende machst du es beschreibbar ..
(rekursiv mit GNU
chmod
).
Skripte, die die automatische Verarbeitung von Dateien in öffentlich beschreibbaren Bereichen durchführen /tmp
, müssen sehr sorgfältig geschrieben werden.
Wie wäre es mit [ $# -gt 1 ]
Das finde ich ärgerlich. Einige Leute haben die Mühe, sich zu fragen, ob eine bestimmte Erweiterung problematisch sein könnte, um zu entscheiden, ob sie die Anführungszeichen weglassen können.
Es ist wie zu sagen. Hey, es sieht so aus, $#
als könnte der split + glob-Operator nicht verwendet werden. Bitten wir die Shell, ihn zu split + globen . Oder Hey, lass uns falschen Code schreiben, nur weil es unwahrscheinlich ist, dass der Fehler auftritt .
Wie unwahrscheinlich ist es jetzt? OK, $#
(oder $!
, $?
oder eine arithmetische Substitution) nur Ziffern enthalten (oder -
für einige) , so dass die glob Teil ist , aus. Damit der aufgeteilte Teil jedoch etwas bewirkt, müssen wir $IFS
nur Ziffern (oder -
) enthalten.
Mit einigen Shells $IFS
kann von der Umgebung geerbt werden, aber wenn die Umgebung nicht sicher ist, ist das Spiel trotzdem vorbei.
Wenn Sie nun eine Funktion schreiben wie:
my_function() {
[ $# -eq 2 ] || return
...
}
Das bedeutet, dass das Verhalten Ihrer Funktion vom Kontext abhängt, in dem sie aufgerufen wird. Oder mit anderen Worten, $IFS
wird einer der Eingänge dazu. Genau genommen sollte es beim Schreiben der API-Dokumentation für Ihre Funktion ungefähr so aussehen:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
Und Code, der Ihre Funktion aufruft, muss sicherstellen, dass er $IFS
keine Ziffern enthält. Das alles, weil Sie keine Lust hatten, diese 2 doppelten Anführungszeichen einzugeben.
Damit dieser [ $# -eq 2 ]
Bug zu einer Sicherheitslücke wird, müsste der Wert von irgendwie $IFS
unter die Kontrolle des Angreifers geraten . Möglicherweise würde dies normalerweise nicht passieren, wenn der Angreifer nicht einen anderen Fehler ausnutzen könnte.
Das ist allerdings nicht ungewöhnlich. Ein häufiger Fall ist, dass Benutzer vergessen, Daten zu bereinigen, bevor sie in arithmetischen Ausdrücken verwendet werden. Wir haben oben bereits gesehen, dass es in einigen Shells die Ausführung von willkürlichem Code ermöglichen kann, aber in allen erlaubt es
dem Angreifer , jeder Variablen einen ganzzahligen Wert zuzuweisen.
Zum Beispiel:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
Und mit einem $1
mit Wert (IFS=-1234567890)
hat diese arithmetische Auswertung den Nebeneffekt, dass IFS gesetzt wird und der nächste [
Befehl fehlschlägt, was bedeutet, dass die Prüfung auf zu viele Argumente umgangen wird.
Was ist, wenn der split + glob- Operator nicht aufgerufen wird?
Es gibt einen anderen Fall, in dem Anführungszeichen für Variablen und andere Erweiterungen erforderlich sind: wenn sie als Muster verwendet werden.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
Testen Sie nicht, ob $a
und gleich $b
sind (außer mit zsh
), sondern ob $a
das Muster mit übereinstimmt $b
. Und Sie müssen zitieren, $b
wenn Sie als Zeichenfolgen vergleichen möchten (dasselbe in "${a#$b}"
oder "${a%$b}"
oder "${a##*$b*}"
wo $b
sollte zitiert werden, wenn es nicht als Muster verwendet werden soll).
Dies bedeutet, dass [[ $a = $b ]]
in Fällen, in denen dies nicht der Fall $a
ist $b
(z. B. wann $a
ist anything
und $b
ist *
), true zurückgegeben werden kann oder false zurückgegeben werden kann, wenn sie identisch sind (z. B. wenn beide $a
und $b
sind [a]
).
Kann dies zu einer Sicherheitslücke führen? Ja, wie jeder Käfer. Hier kann der Angreifer den logischen Codefluss Ihres Skripts ändern und / oder die Annahmen Ihres Skripts brechen. Zum Beispiel mit einem Code wie:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
Der Angreifer kann den Scheck durch Überholen umgehen '[a]' '[a]'
.
Wenn weder dieser Mustervergleich noch der Operator split + glob zutreffen, besteht dann die Gefahr, dass eine Variable nicht in Anführungszeichen gesetzt wird.
Ich muss zugeben, dass ich schreibe:
a=$b
case $a in...
Dort schadet das Zitieren nicht, ist aber nicht zwingend erforderlich.
Ein Nebeneffekt des Weglassens von Anführungszeichen in diesen Fällen (z. B. bei Fragen und Antworten) ist jedoch, dass Anfängern eine falsche Nachricht gesendet werden kann: Möglicherweise ist es in Ordnung, keine Variablen anzugeben .
Zum Beispiel könnten sie anfangen zu denken, dass, wenn a=$b
OK ist, export a=$b
dies auch der Fall ist (was in vielen Shells nicht so ist wie in Argumenten für den export
Befehl, also im Listenkontext) oder env a=$b
.
Was ist zsh
?
zsh
Die meisten dieser Designprobleme wurden behoben. In zsh
(zumindest , wenn sie nicht in sh / KSH - Emulationsmodus), wenn Sie wollen Spaltung oder Globbing oder Pattern - Matching , müssen Sie es explizit fordern: $=var
aufzuspalten, und $~var
für den Inhalt der Variablen zu glob oder wie behandelt werden ein Muster.
Das Aufteilen (aber nicht das Verschieben) erfolgt jedoch implizit nach einer nicht zitierten Befehlssubstitution (wie in echo $(cmd)
).
Ein manchmal unerwünschter Nebeneffekt, wenn keine Variable angegeben wird, ist die Leergutentfernung . Das zsh
Verhalten ähnelt dem, was Sie in anderen Shells erreichen können, indem Sie das Globbing insgesamt (mit set -f
) und das Teilen (mit IFS=''
) deaktivieren . Immer noch in:
cmd $var
Es wird kein split + glob geben , aber wenn $var
leer, anstatt ein leeres Argument zu erhalten, cmd
wird überhaupt kein Argument erhalten.
Das kann Fehler verursachen (wie das Offensichtliche [ -n $var ]
). Das kann möglicherweise die Erwartungen und Annahmen eines Skripts brechen und Sicherheitslücken verursachen, aber ich kann mir gerade kein nicht allzu weit hergeholtes Beispiel einfallen lassen.
Was ist, wenn Sie tun das brauchen Split + glob Operator?
Ja, in der Regel möchten Sie Ihre Variable nicht in Anführungszeichen setzen. Aber dann müssen Sie sicherstellen, dass Sie Ihre Split- und Glob- Operatoren richtig einstellen , bevor Sie sie verwenden. Wenn Sie nur den geteilten Teil und nicht den Glob- Teil (was meistens der Fall ist) möchten , müssen Sie das Globbing ( set -o noglob
/ set -f
) deaktivieren und korrigieren $IFS
. Andernfalls verursachen Sie ebenfalls Sicherheitslücken (wie das oben erwähnte CGI-Beispiel von David Korn).
Fazit
Kurz gesagt, es kann sehr gefährlich sein, eine Variable (oder eine Befehlssubstitution oder eine arithmetische Erweiterung) ohne Anführungszeichen in Shells zu lassen, insbesondere wenn sie in falschen Kontexten ausgeführt wird.
Das ist einer der Gründe, warum es als schlechte Praxis gilt .
Vielen Dank fürs Lesen. Wenn es dir über den Kopf geht, mach dir keine Sorgen. Man kann nicht erwarten, dass jeder versteht, was es bedeutet, seinen Code so zu schreiben, wie er geschrieben wurde. Aus diesem Grund haben wir
Empfehlungen für bewährte Praktiken , sodass diese befolgt werden können, ohne unbedingt zu verstehen, warum.
(Falls dies noch nicht offensichtlich ist, vermeiden Sie es, sicherheitsrelevanten Code in Shells zu schreiben.)
Und bitte geben Sie Ihre Variablen in Ihren Antworten auf dieser Website an!
¹In ksh93
und pdksh
und Ableitungen wird die Erweiterung von geschweiften Klammern auch durchgeführt, sofern das Globbing nicht deaktiviert ist (bei ksh93
Versionen bis zu ksh93u +, auch wenn die braceexpand
Option deaktiviert ist).