"Führen Sie jeden Befehl aus, der nicht vertrauenswürdige Daten an Befehle weitergibt, die Argumente als Befehle interpretieren."

18

Aus dem findutils-Handbuch:

Zum Beispiel Konstrukte wie diese beiden Befehle

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

sind sehr gefährlich. Der Grund dafür ist, dass das '{}' zu einem Dateinamen erweitert wird, der ein Semikolon oder andere Sonderzeichen für die Shell enthalten kann. Wenn zum Beispiel jemand die Datei erstellt, können /tmp/foo; rm -rf $HOMEdie beiden obigen Befehle das Ausgangsverzeichnis eines anderen Benutzers löschen.

Führen Sie aus diesem Grund keinen Befehl aus, der nicht vertrauenswürdige Daten (z. B. Dateinamen) an Befehle weitergibt, die Argumente als weiter zu interpretierende Befehle interpretieren (z. B. 'sh').

Im Fall der Shell gibt es eine clevere Lösung für dieses Problem:

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

Es ist nicht garantiert, dass dieser Ansatz jedes Problem vermeidet, aber er ist viel sicherer, als Daten nach Wahl eines Angreifers in den Text eines Shell-Befehls zu ersetzen.

  1. Liegt die Ursache des Problems darin, find -exec sh -c "something {}" \;dass der Ersatz für {}nicht in Anführungszeichen gesetzt und daher nicht als einzelne Zeichenfolge behandelt wird?
  2. In der Lösung find -exec sh -c 'something "$@"' sh {} \;,

    • zuerst {}wird ersetzt, aber da {}nicht zitiert, hat auch nicht "$@"das gleiche Problem wie der ursprüngliche Befehl? Zum Beispiel "$@"wird erweitert werden "/tmp/foo;", "rm", "-rf"und "$HOME"?

    • Warum wird {}nicht geflüchtet oder zitiert?

  3. Könnten Sie andere Beispiele nennen (immer noch mit sh -coder ohne, falls zutreffend; mit oder ohne, findwas möglicherweise nicht erforderlich ist), bei denen die gleiche Art von Problem und Lösung zutrifft und die minimale Beispiele sind, damit wir uns auf das Problem und die Lösung mit konzentrieren können? wenig ablenkung wie möglich? Siehe Möglichkeiten, Argumente für einen Befehl bereitzustellen, der von `bash -c` ausgeführt wird

Vielen Dank.

Tim
quelle

Antworten:

29

Dies bezieht sich nicht wirklich auf Zitate, sondern auf die Verarbeitung von Argumenten.

Betrachten Sie das riskante Beispiel:

find -exec sh -c "something {}" \;
  • Dies wird durch die Schale analysiert und aufgeteilt in sechs Worte: find, -exec, sh, -c, something {}(ohne Anführungszeichen mehr) ;. Es gibt nichts zu erweitern. Die Shell wird findmit diesen sechs Wörtern als Argumente ausgeführt.

  • Wenn findFunde etwas zu verarbeiten, sagen wir foo; rm -rf $HOME, ersetzt es {}mit foo; rm -rf $HOMEund läuft shmit den Argumenten sh, -cund something foo; rm -rf $HOME.

  • shJetzt wird angezeigt -c, und als Ergebnis wird das Ergebnis analysiert something foo; rm -rf $HOME( das erste nicht optionale Argument ) und ausgeführt.

Betrachten Sie nun die sicherere Variante:

find -exec sh -c 'something "$@"' sh {} \;
  • Die Schalenläufe findmit den Argumenten find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Nun , wenn findFunde foo; rm -rf $HOME, ersetzt sie {}wieder, und läuft shmit den Argumenten sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • shsieht -cund analysiert something "$@"als auszuführenden Befehl shund foo; rm -rf $HOMEals Positionsparameter ( beginnend mit$0 ), erweitert sich "$@"zu foo; rm -rf $HOME einem einzelnen Wert und wird somethingmit dem einzelnen Argument ausgeführt foo; rm -rf $HOME.

Sie können dies sehen, indem Sie verwenden printf. Erstellen Sie ein neues Verzeichnis, geben Sie es ein und führen Sie es aus

touch "hello; echo pwned"

Führen Sie die erste Variante wie folgt aus

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

produziert

Argument: .
Argument: ./hello
pwned

während die zweite Variante läuft als

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

produziert

Argument: .
Argument: ./hello; echo pwned
Stephen Kitt
quelle
Warum wird in der sichereren Variante {}nicht geflüchtet oder zitiert?
Tim
1
Es muss im Allgemeinen nicht zitiert werden. Es müsste nur in einer Shell angegeben werden, in der es eine andere Bedeutung hat, und ich denke, dass dies bei keiner der heute verwendeten Haupt-Shells der Fall ist.
Stephen Kitt
Von gnu.org/software/findutils/manual/html_mono/… : "Diese beiden Konstruktionen ( ;und {}) müssen mit einem '\' versehen oder in Anführungszeichen gesetzt werden, um sie vor der Erweiterung durch die Shell zu schützen." Meinst du, dass es für Bash falsch ist?
Tim
3
Ich meine, dass es für Muscheln im Allgemeinen falsch ist. Siehe POSIX . Diese besondere Aussage im findutilsHandbuch stammt mindestens aus dem Jahr 1996 ... Natürlich schadet es nicht {}, entweder als '{}'oder zu zitieren "{}", aber es ist nicht notwendig.
Stephen Kitt
@Tim Sie haben eine häufige Situation, in der Ihre Intuition die Tatsache noch nicht berücksichtigt, dass hier zwei sehr unterschiedliche Arten der Argumentverarbeitung ablaufen: wann / wie die Shell Argumente aus einer Textzeile parst (welche) unterscheidet sich von der unbearbeiteten Übergabe von Betriebssystemargumenten (wobei Anführungszeichen nur reguläre Zeichen sind). Im Shell-Befehl find some/path -exec sh -c 'something "$@"' {} \;gibt es tatsächlich drei Ebenen für die Verarbeitung / Übergabe von Argumenten, zwei der Shell-Variante und eine der Basis-Raw-Betriebssystemvarianten.
mtraceur
3

Teil 1:

find Verwendet nur Textersetzung.

Ja, wenn Sie es ohne Anführungszeichen getan haben:

find . -type f -exec sh -c "echo {}" \;

und ein Angreifer konnte eine Datei mit dem Namen erstellen ; echo owned, die dann ausgeführt wurde

sh -c "echo ; echo owned"

was dazu führen würde, dass die Shell echodann läuft echo owned.

Wenn Sie jedoch Anführungszeichen hinzugefügt haben, kann der Angreifer Ihre Anführungszeichen einfach beenden und anschließend den böswilligen Befehl ausführen, indem er eine Datei mit dem Namen erstellt '; echo owned:

find . -type f -exec sh -c "echo '{}'" \;

die in der Schale Lauf zur Folge hätte echo '', echo owned.

(Wenn Sie die doppelten Anführungszeichen gegen einfache Anführungszeichen austauschen, kann der Angreifer auch den anderen Anführungszeichentyp verwenden.)


Teil 2:

In find -exec sh -c 'something "$@"' sh {} \;wird das {}von der Shell zunächst nicht interpretiert, sondern direkt mit ausgeführt execve, sodass das Hinzufügen von Shell-Anführungszeichen nicht hilfreich ist.

find -exec sh -c 'something "$@"' sh "{}" \;

hat keine Auswirkung, da die Shell die doppelten Anführungszeichen entfernt, bevor sie ausgeführt wird find.

find -exec sh -c 'something "$@"' sh "'{}'" \;

Fügt Anführungszeichen hinzu, die von der Shell nicht speziell behandelt werden. In den meisten Fällen bedeutet dies lediglich, dass der Befehl nicht das tut, was Sie möchten.

Nachdem es zu erweitern /tmp/foo;, rm, -rf, $HOMEsollte kein Problem sein, denn das sind Argumente something, und somethingwahrscheinlich nicht behandelt ihre Argumente als Befehle auszuführen.


Teil 3:

Ich gehe davon aus, dass ähnliche Überlegungen für alles gelten, was nicht vertrauenswürdige Eingaben entgegennimmt und als (Teil von) einem Befehl ausführt, zum Beispiel xargsund parallel.

Mikel
quelle
xargsist nur gefährlich, wenn -Ioder -Jverwendet wird. Im normalen Betrieb werden nur Argumente an das Ende der Liste angehängt, genau wie dies der -exec ... {} +Fall ist.
Charles Duffy
1
(Im parallelGegensatz dazu wird standardmäßig eine Shell ohne ausdrückliche Aufforderung des Benutzers ausgeführt, wodurch die anfällige Oberfläche vergrößert wird. Dies ist ein Thema, über das viel diskutiert wurde. Eine Erklärung finden Sie unter unix.stackexchange.com/questions/349483/… .) und den enthaltenen Link zu lists.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html ).
Charles Duffy
@CharlesDuffy Du meinst " die gefährdete Oberfläche verkleinern ". Der von OP dargestellte Angriff funktioniert in der Tat nicht standardmäßig mit GNU Parallel.
Ole Tange
1
Ja, ich weiß, dass Sie es für die Parität über SSH benötigen - wenn Sie keinen Remote- parallel"Empfänger" -Prozess am anderen Ende eines Sockets erzeugt haben, der in der Lage ist, einen direkten Vorgang ohne Shell durchzuführen execv. Welches ist genau das, was ich getan hätte, wenn Sie das Tool implementiert hätten. Ein nicht interaktives Programm zu haben, das sich auf der Grundlage des aktuellen Werts von anders verhält, SHELList kaum zu überraschen.
Charles Duffy
1
Ja, ich bin der Meinung, dass Pipes und Umleitungen nur dann möglich sein sollten , wenn der Benutzer eine Shell explizit startet. An diesem Punkt erhalten sie nur das explizite Verhalten der Shell, die sie explizit gestartet haben. Wenn man nur das erwarten kann, was ein execvanbietet, ist das einfacher und weniger erstaunlich und eine Regel, die sich nicht über Standorte / Laufzeitumgebungen hinweg ändert.
Charles Duffy
3

1. Liegt die Ursache des Problems darin, find -exec sh -c "something {}" \;dass der Ersatz für {}nicht in Anführungszeichen gesetzt und daher nicht als einzelne Zeichenfolge behandelt wird?

In gewissem Sinne kann aber das Zitieren hier nicht helfen . Der Dateiname, der anstelle von ersetzt wird, {}kann einen beliebigen enthalten Zeichen , einschließlich Anführungszeichen . Unabhängig von der Art der Anführungszeichen kann der Dateiname dasselbe enthalten und die Anführungszeichen "ausbrechen".

2. ... aber da {}es nicht zitiert ist, hat es nicht "$@"auch das gleiche Problem wie der ursprüngliche Befehl? Zum Beispiel "$@"wird erweitert auf "/tmp/foo;", "rm", "-rf", and "$HOME"?

Nr. "$@"Erweitert die Positionsparameter als separate Wörter und teilt sie nicht weiter auf. Hier {}ist ein Argument für findsich und findübergibt den aktuellen Dateinamen auch als eindeutiges Argument an sh. Es ist direkt als Variable im Shell-Skript verfügbar und wird nicht als Shell-Befehl selbst verarbeitet.

... warum wird {}nicht geflüchtet oder zitiert?

Es muss nicht sein, in den meisten Muscheln. Wenn Sie ausführen fish, muss es sein: fish -c 'echo {}'Druckt eine leere Zeile. Aber es spielt keine Rolle, ob Sie es zitieren, die Shell entfernt nur die Anführungszeichen.

3. Können Sie andere Beispiele nennen ...

Jedes Mal, wenn Sie einen Dateinamen (oder eine andere unkontrollierte Zeichenfolge) so wie er ist in einer Zeichenfolge erweitern, die als eine Art Code betrachtet wird (*) interpretiert wird, besteht die Möglichkeit einer beliebigen Befehlsausführung.

Dies erweitert beispielsweise $fdirekt den Perl-Code und führt zu Problemen, wenn ein Dateiname ein doppeltes Anführungszeichen enthält. Das Anführungszeichen im Dateinamen beendet das Anführungszeichen im Perl-Code, und der Rest des Dateinamens kann einen beliebigen Perl-Code enthalten:

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(Der Dateiname muss etwas seltsam sein, da Perl den gesamten Code im Voraus analysiert, bevor er ausgeführt wird. Daher müssen wir einen Analysefehler vermeiden.)

Während dies sicher durch ein Argument geht:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(Es ist nicht sinnvoll, eine andere Shell direkt von einer Shell aus auszuführen, aber wenn Sie dies tun, ist es ähnlich wie die find -exec sh ... Fall.)

(* Ein Code enthält SQL, daher obligatorisches XKCD: https://xkcd.com/327/ plus Erläuterung: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )

ilkkachu
quelle
1

Ihr Anliegen ist genau der Grund, warum GNU Parallel Eingaben anführt:

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

Dies wird nicht ausgeführt echo pwned.

Es wird eine Shell ausgeführt, sodass Sie nicht plötzlich eine Überraschung erleben, wenn Sie Ihren Befehl erweitern:

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

Weitere Informationen zum Thema "Laichen einer Shell" finden Sie unter: https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell

Ole Tange
quelle
1
... das heißt, find . -print0 | xargs -0 printf "Argument: %s\n"ist genauso sicher (oder besser gesagt, mehr, da -print0man mit Dateinamen mit Zeilenumbrüchen richtig umgeht); Das parallele Zitieren ist eine Problemumgehung für ein Problem , das überhaupt nicht existiert, wenn keine Shell vorhanden ist.
Charles Duffy
Sobald Sie | wcjedoch den Befehl erweitern, müssen Sie durch die Rahmen springen, um die Sicherheit zu gewährleisten. GNU Parallel ist standardmäßig sicher.
Ole Tange
ITYM: Es versucht, jede Eigenart der Shell-Sprache durch den komplizierten Prozess des sorgfältigen Zitierens zu überdecken. Das ist bei weitem nicht so sicher wie das ordnungsgemäße Speichern von Daten in Variablen, die nicht mit Code vermischt sind. Wenn das nun automatisch foo {} | wcin sh -c 'foo "$1" | wcsh {} ` konvertiert würde , könnte das anders sein.
ilkkachu