Auswirkungen auf die Sicherheit, wenn Sie vergessen, eine Variable in Bash / POSIX-Shells anzugeben

206

Wenn Sie unix.stackexchange.com schon länger folgen, sollten Sie jetzt hoffentlich wissen, dass das Nichtanführen einer Variablen im Listenkontext (wie in echo $var) in Bourne / POSIX-Shells (zsh ist die Ausnahme) eine ganz besondere Bedeutung hat und sollte nicht gemacht werden, es sei denn, Sie haben einen sehr guten Grund dazu.

Es wird ausführlich in einer Reihe von Fragen und Antworten hier (Beispiele erläutert: ? Warum kommt der Shell - Skript Drossel auf Leerzeichen oder andere Sonderzeichen , wenn doppelt zitiert notwendig? , Erweiterung einer Shell - Variablen und Wirkung von glob und Split auf es , Zitat vs unquoted string expansion)

Dies war der Fall seit der ersten Veröffentlichung der Bourne-Shell in den späten 70ern und wurde von der Korn-Shell (eines der größten Bedauern von David Korn (Frage 7) ) nicht geändert oder bashhat die Korn-Shell größtenteils kopiert, und das ist wie das von POSIX / Unix festgelegt wurde.

Jetzt sehen wir hier immer noch eine Reihe von Antworten und sogar gelegentlich öffentlich veröffentlichten Shell-Code, in dem Variablen nicht in Anführungszeichen gesetzt werden. Sie hätten gedacht, die Leute hätten inzwischen gelernt.

Nach meiner Erfahrung gibt es hauptsächlich drei Arten von Personen, die es unterlassen, ihre Variablen anzugeben:

  • Anfänger. Diese können entschuldigt werden, da es sich zugegebenermaßen um eine völlig unintuitive Syntax handelt. Und es ist unsere Aufgabe auf dieser Seite, sie auszubilden.

  • vergessliche Leute.

  • Leute, die auch nach mehrmaligem Hämmern nicht überzeugt sind, die denken, dass der Bourne-Shell-Autor sicherlich nicht beabsichtigt hat, alle unsere Variablen zu zitieren .

Vielleicht können wir sie überzeugen, wenn wir das mit solchen Verhaltensweisen verbundene Risiko aufdecken.

Was ist das Schlimmste, was passieren kann, wenn Sie vergessen, Ihre Variablen anzugeben? Ist es wirklich so schlimm?

Von welcher Art von Verwundbarkeit sprechen wir hier?

In welchen Zusammenhängen kann es ein Problem sein?

Stéphane Chazelas
quelle
8
BashPitfalls gefällt Ihnen bestimmt.
Pawel7318
backlink von diesem artikel, den ich geschrieben habe , danke für die beschreibung
mirabilos
5
Ich möchte vorschlagen, eine vierte Gruppe hinzuzufügen: Menschen, die zu oft wegen exzessiven Zitierens über den Kopf geschlagen wurden, möglicherweise von Mitgliedern der dritten Gruppe, die ihre Frustration über andere (das Opfer wird zum Mobber) ausüben. Das Traurige ist natürlich, dass diejenigen aus der vierten Gruppe möglicherweise Dinge nicht zitieren, wenn es am wichtigsten ist.
ack

Antworten:

201

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)))

$varwird zuerst in eine Liste von Wörtern nach komplexen Regeln aufgeteilt, die den $IFSspeziellen 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 $varenthält *.txt,/var/*.xmlund $IFS enthält ,, wird cmddies mit einer Reihe von Argumenten aufgerufen, wobei das erste cmdund das nächste die txt Dateien im aktuellen Verzeichnis und die xmlDateien in sind /var.

Wenn Sie nur cmdmit 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/ pythonCGI-Skript eine Shell auf, um diese Befehlszeile zu interpretieren (ganz zu schweigen von der Tatsache, dass es cmdsich 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 /tmpoder ~/tmp).

Sogar ein ~/.bashrckann verwundbar sein (zum Beispiel bashwird es interpretiert, wenn es aufgerufen wird ssh, um ForcedCommand in gitServerbereitstellungen 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_inputenthä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/*/.forwardfü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, $QUERYSTRINGwenn nicht gesetzt $QUERYSTRING war. Dies ist eine schnelle Möglichkeit, ein CGI-Skript auch über die Befehlszeile aufrufbar zu machen.

Das $QUERYSTRINGist 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_inputShell-Variablen der foo awkVariablen 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, foosondern als awkCode 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, bashoder 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 bashfü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 $IFSnur Ziffern (oder -) enthalten.

Mit einigen Shells $IFSkann 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 $IFSkeine 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 $IFSunter 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 $1mit 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 $aund gleich $bsind (außer mit zsh), sondern ob $adas Muster mit übereinstimmt $b. Und Sie müssen zitieren, $bwenn Sie als Zeichenfolgen vergleichen möchten (dasselbe in "${a#$b}"oder "${a%$b}"oder "${a##*$b*}"wo $bsollte zitiert werden, wenn es nicht als Muster verwendet werden soll).

Dies bedeutet, dass [[ $a = $b ]]in Fällen, in denen dies nicht der Fall $aist $b(z. B. wann $aist anythingund $bist *), true zurückgegeben werden kann oder false zurückgegeben werden kann, wenn sie identisch sind (z. B. wenn beide $aund $bsind [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=$bOK ist, export a=$bdies auch der Fall ist (was in vielen Shells nicht so ist wie in Argumenten für den exportBefehl, also im Listenkontext) oder env a=$b.

Was ist zsh?

zshDie 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: $=varaufzuspalten, und $~varfü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 zshVerhalten ä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 $varleer, anstatt ein leeres Argument zu erhalten, cmdwird ü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 ksh93und pdkshund Ableitungen wird die Erweiterung von geschweiften Klammern auch durchgeführt, sofern das Globbing nicht deaktiviert ist (bei ksh93Versionen bis zu ksh93u +, auch wenn die braceexpandOption deaktiviert ist).

Stéphane Chazelas
quelle
Beachten Sie, dass mit [[nur die RHS der Vergleiche angegeben werden muss:if [[ $1 = "$2" ]]; then
mirabilos
2
@mirabilos, ja, aber das muss nicht LHS nicht zitiert werden , so gibt es keinen zwingenden Grund , es nicht dort zu zitieren (wenn wir die bewusste Entscheidung zu treffen sind , um standardmäßig zu zitieren , wie es das Vernünftigste , was zu sein scheint zu tun ). Beachten Sie auch, dass dies [[ $* = "$var" ]]nicht dasselbe ist, als [[ "$*" = "$var" ]]wenn das erste Zeichen von $IFSkein Leerzeichen ist bash(und auch mkshwenn $IFSleer ist, obwohl ich in diesem Fall nicht sicher bin, was $*gleich ist, sollte ich einen Fehler auslösen?)).
Stéphane Chazelas
1
Ja, Sie können dort standardmäßig zitieren. Bitte keine Fehler mehr bei der Feldaufteilung, ich muss noch die Fehler beheben, die mir bekannt sind (von Ihnen und anderen), bevor wir dies erneut bewerten können.
Mirabilos
2
@Barmar, vorausgesetzt, Sie meinen, dies würde foo='bar; rm *'nicht der Fall sein. Es wird jedoch der Inhalt des aktuellen Verzeichnisses aufgeführt, der als Offenlegung von Informationen gelten kann. print $fooin ksh93(wo printist der Ersatz für echodiese Adressen einige seiner Mängel) hat zwar eine Code-Injection-Schwachstelle (zum Beispiel mit foo='-f%.0d z[0$(uname>&2)]') (Sie tatsächlich brauchen print -r -- "$foo". echo "$foo"ist immer noch falsch und nicht behebbar (obwohl im Allgemeinen weniger schädlich)).
Stéphane Chazelas
3
Ich bin kein Bash-Experte, aber ich programmiere seit über einem Jahrzehnt darin. Ich habe oft Anführungszeichen verwendet, aber hauptsächlich, um eingebettete Leerzeichen zu verarbeiten. Jetzt benutze ich sie viel mehr! Es wäre schön, wenn jemand auf diese Antwort eingehen würde, um es ein wenig einfacher zu machen, alle Feinheiten zu erfassen. Ich habe viel davon, aber ich habe auch viel verpasst. Es ist schon ein langer Beitrag, aber ich weiß, dass ich hier noch viel mehr lernen kann. Vielen Dank!
Joe
34

[Inspiriert von dieser Antwort von cas .]

Aber was wenn …?

Was aber, wenn mein Skript eine Variable auf einen bekannten Wert setzt, bevor ich sie verwende? Was ist insbesondere, wenn eine Variable auf einen von zwei oder mehr möglichen Werten gesetzt wird (aber immer auf etwas Bekanntes) und keiner der Werte Leerzeichen oder Glob-Zeichen enthält? Ist es in diesem Fall nicht sicher, es ohne Anführungszeichen zu verwenden ?

Und was ist, wenn einer der möglichen Werte die leere Zeichenfolge ist und ich auf "Leergutentfernung" angewiesen bin? Dh, wenn die Variable die leere Zeichenfolge enthält, möchte ich die leere Zeichenfolge nicht in meinem Befehl abrufen. Ich will nichts bekommen. Zum Beispiel,

wenn irgendein_Zustand
dann
    ignorecase = "- i"
sonst
    ignorecase = ""
fi
                                        # Beachten Sie, dass die Anführungszeichen in den obigen Befehlen nicht unbedingt erforderlich sind. 
grep $ ignorecase   other_ grep _args

Kann ich nicht sagen ; Das schlägt fehl, wenn die Zeichenfolge leer ist.grep "$ignorecase" other_grep_args$ignorecase

Antwort:

Wie in der anderen Antwort besprochen, schlägt dies immer noch fehl, wenn IFSein -oder ein enthalten ist i. Wenn Sie sichergestellt haben IFS, dass Ihre Variable kein Zeichen enthält (und Sie sicher sind, dass Ihre Variable keine Glob-Zeichen enthält), ist dies wahrscheinlich sicher.

Aber es gibt einen sichereren Weg (obwohl er etwas hässlich und nicht intuitiv ist): Verwenden ${ignorecase:+"$ignorecase"}. Von der POSIX - Shell Command Language - Spezifikation unter  2.6.2 Parameter Expansion ,

${parameter:+[word]}

    Verwenden Sie einen alternativen Wert.   Wenn parameternicht gesetzt oder null ist, wird null ersetzt; Andernfalls wird die Erweiterung von word (oder eine leere Zeichenfolge, wenn diese wordweggelassen wird) ersetzt.

Der Trick dabei ist, dass wir ignorecaseals parameter und "$ignorecase"als verwenden word. Also ${ignorecase:+"$ignorecase"}heißt

Wenn $ignorecasenicht gesetzt oder null (dh leer) ist, wird null (dh nichts ohne Anführungszeichen ) ersetzt. ansonsten ist die Erweiterung von "$ignorecase"zu ersetzen.

Das bringt uns dahin, wohin wir wollen: Wenn die Variable auf die leere Zeichenfolge gesetzt ist, wird sie "entfernt" (dieser gesamte, verschachtelte Ausdruck wird zu nichts ausgewertet - nicht einmal zu einer leeren Zeichenfolge), und wenn die Variable eine Nicht-Zeichenfolge enthält -leerer Wert, wir bekommen diesen Wert in Anführungszeichen.


Aber was wenn …?

Aber was ist, wenn ich eine Variable habe, die ich in Wörter aufteilen möchte / muss? (Dies ist ansonsten wie im ersten Fall der Fall. Mein Skript hat die Variable gesetzt, und ich bin sicher, dass sie keine Glob-Zeichen enthält. Sie kann jedoch Leerzeichen enthalten, und ich möchte, dass sie in separate Argumente am Leerzeichen aufgeteilt wird Grenzen
PS Ich möchte immer noch Leergut entfernen.)

Zum Beispiel,

wenn irgendein_Zustand
dann
    Kriterien = "- Typ f"
sonst
    Kriterien = ""
fi
wenn irgendein_anderer_Zustand
dann
    criterion = "$ criterion -mtime +42"
fi
find "$ start_directory" $   criterions other_ find _args

Antwort:

Sie könnten denken, dass dies ein Fall für die Verwendung ist eval.  Nein!   Widerstehen Sie der Versuchung, auch nur über die Verwendung evalhier nachzudenken .

Auch wenn Sie sichergestellt haben, dass IFSIhre Variable kein Zeichen enthält (mit Ausnahme der Leerzeichen, die Sie berücksichtigen möchten) und Sie sicher sind, dass Ihre Variable keine Glob-Zeichen enthält, ist dies wahrscheinlich der Fall sicher.

Wenn Sie jedoch bash (oder ksh, zsh oder yash) verwenden, ist Folgendes sicherer: Verwenden Sie ein Array:

wenn irgendein_Zustand
dann
    criterions = (- type f) # Man könnte sagen `criterions = (" - type "" f ")`, aber es ist wirklich unnötig.
sonst
    Kriterien = () # Sie nicht alle Angebote auf diesen Befehl verwenden!
fi
wenn irgendein_anderer_Zustand
dann
    Kriterien + = (- mtime +42) # Hinweis: Nicht `=`, sondern ` + =`, um ein Array hinzuzufügen (anzuhängen).
fi
find "$ start_directory" "$ {criterion [@]}"   other_ find _args

Von bash (1) ,

Jedes Element eines Arrays kann mit referenziert werden . ... Wenn ist oder  , wird das Wort auf alle Mitglieder von . Diese Indizes unterscheiden sich nur, wenn das Wort in doppelten Anführungszeichen steht. Wenn das Wort in doppelte Anführungszeichen gesetzt ist, erweitert … jedes Element von zu einem separaten Wort.${name[subscript]}subscript@*name${name[@]}name

So "${criteria[@]}"dehnt sich (im obigen Beispiel) , die null, zwei oder vier Elemente des criteriaArrays, die jeweils angegeben. Insbesondere, wenn keine der Bedingungen  s wahr ist, hat das criteriaArray keinen Inhalt (wie in der criteria=()Anweisung festgelegt) und wird "${criteria[@]}"zu nichts ausgewertet (nicht einmal zu einer unbequemen leeren Zeichenfolge).


Dies wird besonders interessant und kompliziert, wenn Sie mit mehreren Wörtern arbeiten, von denen einige dynamische (Benutzer-) Eingaben sind, die Sie nicht im Voraus kennen und die Leerzeichen oder andere Sonderzeichen enthalten können. Erwägen:

printf "Geben Sie den zu suchenden Dateinamen ein:"
lese fname
if ["$ fname"! = ""]
dann
    Kriterien + = (- Name "$ fname")
fi

Beachten Sie, dass dies $fnamebei jeder Verwendung in Anführungszeichen steht. Dies funktioniert auch, wenn der Benutzer etwas wie foo baroder eingibt foo*"${criteria[@]}"wertet zu -name "foo bar"oder aus -name "foo*". (Denken Sie daran, dass jedes Element des Arrays in Anführungszeichen steht.)

Arrays funktionieren nicht in allen POSIX-Shells. Arrays sind ksh / bash / zsh / yash-ism. Außer… es gibt ein Array, das alle Shells unterstützen: die Argumentliste, auch bekannt als "$@". Wenn Sie mit der Argumentliste fertig sind, mit der Sie aufgerufen wurden (z. B. haben Sie alle „Positionsparameter“ (Argumente) in Variablen kopiert oder auf andere Weise verarbeitet), können Sie die Argumentliste als Array verwenden:

wenn irgendein_Zustand
dann
    set - -type f # Man könnte `set -" -type "" f "` sagen, aber das ist wirklich unnötig.
sonst
    einstellen --
fi
wenn irgendein_anderer_Zustand
dann
    setze - "$ @" -Zeit +42
fi
# Ähnlich: set - "$ @" -name "$ fname"
find "$ start_directory" "$ @"   other_ find _args

Das "$@"Konstrukt (das historisch gesehen an erster Stelle stand) hat die gleiche Semantik wie - es erweitert jedes Argument (dh jedes Element der Argumentliste) zu einem separaten Wort, als hätten Sie es eingegeben ."${name[@]}""$1" "$2" "$3" …

Auszug aus der Spezifikation der POSIX Shell Command Language unter 2.5.2 Special Parameters ,

@

    Erweitert die Positionsparameter von eins beginnend und erzeugt zunächst ein Feld für jeden festgelegten Positionsparameter. Die ursprünglichen Felder sind als separate Felder beizubehalten. Wenn keine Positionsparameter vorhanden sind, erzeugt die Erweiterung von @Nullfelder, auch wenn sie @in doppelten Anführungszeichen steht. …

Der vollständige Text ist etwas kryptisch; Der entscheidende Punkt ist, dass angegeben wird, dass "$@"Nullfelder generiert werden sollen, wenn keine Positionsparameter vorhanden sind. Historischer Hinweis: Als es "$@"1979 zum ersten Mal in der Bourne-Shell (dem Vorgänger von Bash) eingeführt wurde, gab es einen Fehler, "$@"der durch eine einzelne leere Zeichenfolge ersetzt wurde, wenn keine Positionsparameter vorhanden waren. Siehe Was ${1+"$@"}bedeutet in einem Shell-Skript und wie unterscheidet es sich von "$@"? The Traditional Bourne Shell FamilyWas ${1+"$@"}bedeutet ... und wo ist es notwendig? und "$@"versus${1+"$@"} .


Arrays helfen auch in der ersten Situation:

wenn irgendein_Zustand
dann
    ignorecase = (- i) # Man könnte "ignorecase = (" - i ")" sagen, aber das ist wirklich unnötig.
sonst
    ignorecase = () # Sie nicht alle Angebote auf diesen Befehl verwenden!
fi
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS (csh)

Dies sollte selbstverständlich sein, aber für die Leute, die hier neu sind: csh, tcsh usw., sind keine Bourne / POSIX-Shells. Sie sind eine ganz andere Familie. Ein Pferd einer anderen Farbe. Ein ganz anderes Ballspiel. Eine andere Katzenrasse. Vögel einer anderen Feder. Und vor allem eine andere Dose Würmer.

Einige der Aussagen auf dieser Seite beziehen sich auf csh. Zum Beispiel: Es ist eine gute Idee, alle Variablen in Anführungszeichen zu setzen, es sei denn, Sie haben einen guten Grund, dies nicht zu tun, und Sie sind sicher, dass Sie wissen, was Sie tun. Aber in csh ist jede Variable ein Array - es kommt einfach so vor, dass fast jede Variable ein Array von nur einem Element ist und sich ziemlich ähnlich wie eine gewöhnliche Shell-Variable in Bourne / POSIX-Shells verhält. Und die Syntax ist schrecklich anders (und ich meine schrecklich ). Deshalb werden wir hier nichts mehr über die csh-Familie sagen.

G-Man
quelle
1
Beachten Sie, dass cshSie in lieber $var:qals verwenden, "$var"da letzteres nicht für Variablen funktioniert, die Zeilenumbruchzeichen enthalten (oder wenn Sie die Elemente des Arrays einzeln in Anführungszeichen setzen möchten, anstatt sie mit Leerzeichen zu einem einzigen Argument zu verbinden).
Stéphane Chazelas
Funktioniert in bash bitrate="--bitrate 320"mit ${bitrate:+$bitrate}und bitrate=--bitrate 128wird nicht funktionieren, ${bitrate:+"$bitrate"}da der Befehl abgebrochen wird. Ist es sicher ${variable:+$variable}mit nein zu benutzen "?
Freedo
@Freedo: Ich finde deinen Kommentar unklar. Mir ist besonders unklar, was Sie wissen wollen, was ich noch nicht gesagt habe. Siehe die zweite Überschrift "Aber was ist, wenn" - "Aber was ist, wenn ich eine Variable habe, die ich in Wörter aufteilen möchte / muss?" Das ist Ihre Situation, und Sie sollten den Ratschlägen dort folgen. Aber wenn Sie dies nicht können (z. B. weil Sie eine andere Shell als bash, ksh, zsh oder yash verwenden und Sie  $@für etwas anderes verwenden) oder Sie sich einfach aus anderen Gründen weigern, ein Array zu verwenden, verweise ich darauf Sie zu "Wie in der anderen Antwort besprochen". … (Fortsetzung)
G-Man
(Fortsetzung) ... Ihr Vorschlag (mit ${variable:+$variable}ohne ") fehl , wenn IFS enthält  -,  0, 2, 3, a, b, e, i, roder  t.
G-Man
Nun, meine Variable hat sowohl a, e, b, i 2 und 3 Zeichen und funktioniert immer noch gut mit${bitrate:+$bitrate}
Freedo
11

Ich war skeptisch gegenüber Stéphanes Antwort, es ist jedoch möglich, Folgendes zu missbrauchen $#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

oder $ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

Dies sind erfundene Beispiele, aber das Potenzial besteht.

Steven Penny
quelle