Wie kann ich sicher sein, dass eine Variable nur einen gültigen Dateinamen enthält?

8

Wie kann ich angesichts des folgenden Skripts sicherstellen, dass das Argument nur einen gültigen Dateinamen enthält /home/charlesingalls/und keinen Pfad ( ../home/carolineingalls/) oder Platzhalter usw.?

Ich möchte nur, dass das Skript eine einzelne Datei aus dem angegebenen fest codierten Verzeichnis löschen kann. Dieses Skript wird als privilegierter Benutzer ausgeführt.

#!/bin/bash

rm -f /home/charlesingalls/"$1"
Aaron Cicali
quelle
2
Wenn Sie "foo / bar" nicht unterstützen möchten, überprüfen Sie einfach, ob es nicht enthält /. Platzhalter werden nicht in Anführungszeichen interpretiert.
Random832
3
Wenn Sie eine Datei löschen , verwenden Sie sie nicht -rmit rm. rm -rdient zum rekursiven Löschen eines Verzeichnisses und aller darunter liegenden Dateien und Verzeichnisse. Dies ist nur beim Löschen von Verzeichnissen hilfreich. Im Allgemeinen nicht Frachtkult. Kopieren Sie also nicht nur Dinge, die nützlich erscheinen, in Ihre Befehlszeile oder Ihr Skript, ohne zu verstehen, was sie tun oder wie sie funktionieren. Die Flugzeuggötter, die die magische Fracht bringen, können wütend werden und alle Ihre Dateien löschen.
Cas
Guter Punkt in Bezug auf die -r-Flagge - ich verstehe ihre Verwendung, habe nur nicht klar gedacht.
Aaron Cicali

Antworten:

7

Wenn Sie nur eine Datei in /home/charlesingalls(und keine Datei in einem Unterverzeichnis) löschen möchten, ist dies ganz einfach: Überprüfen Sie einfach, ob das Argument kein enthält /.

case "$1" in
  */*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac

Dies läuft rmauch , wenn das Argument .oder ..oder leer, aber in diesem Fall rmwird harmlos ausfallen , ein Verzeichnis zu löschen.

Platzhalter sind hier nicht relevant, da keine Platzhaltererweiterung durchgeführt wird.

Dies ist auch bei Vorhandensein symbolischer Links sicher: Wenn es sich bei der Datei um einen symbolischen Link handelt, wird der Symlink (der sich in befindet /home/charlesingalls) entfernt und das Ziel dieses Links wird nicht beeinflusst.

Beachten Sie, dass dies voraussetzt, dass /home/charlesingallsdies nicht verschoben oder geändert werden kann. Dies sollte in Ordnung sein, wenn das Verzeichnis im Skript fest codiert ist. Wenn es jedoch aus Variablen ermittelt wird, ist die Bestimmung zum Zeitpunkt der rmAusführung des Befehls möglicherweise nicht mehr gültig .

Basierend auf den zusätzlichen Informationen, dass das Argument ein virtueller Hostname ist, sollten Sie eine Whitelist anstelle einer Blacklist durchführen: Überprüfen Sie, ob der Name ein sinnvoller virtueller Hostname ist, anstatt nur Schrägstriche zu verbieten. Ich überprüfe, ob der Name mit einem Kleinbuchstaben oder einer Ziffer beginnt und keine anderen Zeichen als Kleinbuchstaben, Ziffern, Punkte und Bindestriche enthält.

LC_CTYPE=C LC_COLLATE=C
case "$1" in
  *[!-.0-9a-z]*|[!0-9a-z]*) echo >&2 "Invalid host name"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac
Gilles 'SO - hör auf böse zu sein'
quelle
In diesem Fall handelt es sich bei den Dateien um Webserver-Konfigurationsdateien, die von einem vorherigen Prozess generiert wurden. Sie haben alle die gleiche Bedeutung (jeder stellt einen virtuellen Host dar). Das Verzeichnis, in dem sie vorhanden sind, grenzt jedoch an ähnliche Verzeichnisse, und alle befinden sich in einem Verzeichnis, das dem Webserver gehört. Dieses spezielle Skript soll das Löschen von Konfigurationen für virtuelle Hosts nur in einem bestimmten Verzeichnis ermöglichen.
Aaron Cicali
Ist dieser Ansatz für Sie sinnvoll, sofern dieser Anwendungsfall vorliegt? Ich schätze Ihre Erfahrung und werde zugeben, dass es definitiv Zeiten gibt, in denen ich "eine Panzerfaust zu einer Schießerei gebracht" habe, um etwas unter Linux zu automatisieren.
Aaron Cicali
@AaronCicali Warum kann das Skript eine der Hostkonfigurationen löschen und nicht nur eine, die zu der Entität gehört, die die ursprüngliche Anforderung gestellt hat? Warum wurde der Name des virtuellen Hosts nicht zuerst überprüft (dann enthielt er keine Sonderzeichen)?
Gilles 'SO - hör auf böse zu sein'
Die Entität, die die ursprüngliche Anforderung stellt, ist eine GUI, die tatsächlich die Berechtigung hat, einen der virtuellen Hosts in diesem Ordner zu entfernen. Der Name des virtuellen Hosts wird zuerst überprüft. Es stammt aus einer Liste zuvor erstellter virtueller Hosts (Domänennamen). Ich glaube, es ist die Aufgabe dieses Skripts, sicherzustellen, dass es so sicher wie möglich ist, ohne sich auf die Sicherheit eines anderen Teils der Anwendung verlassen zu müssen. Das heißt, dass es nur Dateien aus diesem bestimmten Verzeichnis löschen kann. Es müssen auch einige zusätzliche Aufräumarbeiten durchgeführt werden.
Aaron Cicali
@ AaronCicali Ok, als zusätzliche Überprüfung der geistigen Gesundheit ist dies sinnvoll. In diesem Fall sollten Sie eine Whitelist erstellen: Akzeptieren Sie nur Namen, die wie plausible Hostnamen aussehen. Wenn Sie keine Subdomains zulassen, können Sie sogar verbieten.
Gilles 'SO - hören Sie auf, böse zu sein'
10

Diese Antwort setzt voraus, dass $1Unterverzeichnisse enthalten sein dürfen. Wenn Sie an dem einfacheren Fall interessiert sind, bei dem es $1sich um einen einfachen Verzeichnisnamen handeln sollte, lesen Sie eine der anderen Antworten.


Platzhalter werden in doppelten Anführungszeichen nicht erweitert. Da $1es sich um doppelte Anführungszeichen handelt, sind Platzhalter kein Problem.

Sowohl ../als auch Symlinks können den tatsächlichen Speicherort einer Datei verdecken . Im Folgenden werden Tests gezeigt, um festzustellen, ob sich die Datei wirklich und nicht nur scheinbar unter dem gewünschten Pfad befindet.

Neuere Systeme: mit realpath

Um herauszufinden, ob die Datei wirklich unter ist /home/charlesingalls/oder nicht, können Sie Folgendes verwenden realpath:

realpath --relative-base=/home/charlesingalls/ "/home/charlesingalls/$1"  | grep -q '^/' && exit 1

Das Obige wird ausgeführt, exit 1wenn sich die von angegebene Datei an einer $1anderen Stelle als im Verzeichnis befindet /home/charlesingalls/. realpathkanonisiert den gesamten Pfad und eliminiert sowohl Symlinks als auch ../.

realpath ist Teil von GNU coreutils und sollte auf jedem Linux-System verfügbar sein.

realpatherfordert GNU Coreutils 8.15 (Jan 2012) oder besser .

Beispiele

Um zu demonstrieren, wie realpath folgt ../, um den tatsächlichen Speicherort einer Datei zu bestimmen (z. B. wird die -qOption grep weggelassen, damit die tatsächliche Ausgabe von grep sichtbar ist):

$ touch /tmp/test
$ realpath --relative-base=$HOME "$HOME/../../tmp/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Um zu demonstrieren, wie es Symlinks folgt:

$ ln -s /tmp/test ~/test
$ realpath --relative-base=$HOME "$HOME/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Ältere Systeme: mit readlink -e

readlinkist auch in der Lage, einen Pfad zu kononisieren, indem sowohl Symlinks als auch ../:

readlink -e "$HOME/test" | grep -q "^$HOME" || exit 1

Verwenden der gleichen Beispieldateien:

$ readlink -e "$HOME/../../tmp/test" | grep "$HOME" || echo FAIL
FAIL
$ readlink -e "$HOME/test" | grep "^$HOME" || echo FAIL
FAIL

Versionen von readlinksind nicht nur auf älteren GNU-Systemen verfügbar, sondern auch auf BSD.

John1024
quelle
Mein Ubuntu 14.04 coreutilshat nichtrealpath
heemayl
1
Dies scheint eher das zu sein, wonach ich suche, aber leider hat mein Centos 6.5-Server keinen Realpath. Ich bin nicht in der Lage, es zu installieren. Beim Googeln wird erwähnt, dass "readlink -f" den Realpath ersetzt, aber ich muss es noch zum Laufen bringen.
Aaron Cicali
1
Der Untertitel erwähnt -f("alle bis auf die letzte Komponente müssen vorhanden sein") und die verwendeten Beispiele -e("alle Komponenten müssen vorhanden sein"), was etwas verwirrend ist.
Isanae
2
@AaronCicali Dies ist definitiv nicht die Antwort, die Sie brauchen, weil es gefährlich falsch ist. Sie dürfen symbolische Links nicht auflösen . Das Ziel der symbolischen Verknüpfung kann sich zwischen der Überprüfung und der Verwendung ändern. (Es ist eine bekannte Kategorie von Designfehlern ). Außerdem ist es nicht sinnvoll, Symlinks aufzulösen, da sie rmauf das Argument selbst und nicht auf dessen Ziel einwirken.
Gilles 'SO - hör auf böse zu sein'
1
Hinweis: Verwenden Sie grep -qdiese grepOption , um die Ausgabe übereinstimmender Zeilen zu verhindern. Sie erhalten immer noch den Exit-Status &&und arbeiten ||immer noch genau so, wie Sie es gewohnt sind.
ein CVn
2

Wenn Sie Pfade vollständig verbieten möchten, können Sie am einfachsten testen, ob die Variable einen Schrägstrich ( /) enthält. In Bash:

if [[ "$1" = */* ]] ; then...

Dies blockiert jedoch alle Pfade, einschließlich foo/bar. Sie könnten ..stattdessen testen , aber das würde die Möglichkeit lassen, dass Symlinks auf Verzeichnisse außerhalb des Zielpfads verweisen.

Wenn Sie nur das Löschen einer einzelnen Datei zulassen möchten, sollten Sie diese nicht verwenden rm -r.


Abhängig davon, was Sie tun, können Sie auch die Dateiberechtigungen des Systems verwenden, um nur das Löschen von Dateien zuzulassen, die der Benutzer selbst löschen kann. Etwas wie das:

su charlesingalls -c "rm /home/charlesingalls/'$1'"

Obwohl @Gilles dies kommentierte, gibt es ein Problem mit Anführungszeichen: Es schlägt fehl, wenn es $1ein einfaches Anführungszeichen enthält. Daher sollte die Variable zuerst darauf getestet werden (z. B. if [[ "$1" = *\'* ]] ; then fail...durch Whitelist eines sinnvollen Zeichensatzes) oder der übergebene Dateiname eine Umgebungsvariable mit z

file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'
ilkkachu
quelle
Guter Punkt bezüglich der -r Flagge, das sollte wirklich nicht da sein. Jetzt entfernen ...
Aaron Cicali
Beachten Sie, dass Ihr suBefehl fehlerhaft ist, weil das Zitat falsch ist. Sie führen einen Befehl aus, dessen Argument als Shell-Snippet interpoliert ist. Wenn das Argument beispielsweise lautet, $(touch foo)wird Ihr Code ausgeführt touch foo.
Gilles 'SO - hör auf böse zu sein'
@ Gilles, Mist, ich wusste, dass etwas nicht stimmt. verschachtelte Zitate sind nicht mein Lieblingsfreund. Ich denke, die aktuelle Version funktioniert besser, doppelte Anführungszeichen bewerten die Variable in der äußeren Shell und einfache Anführungszeichen verhindern, dass sie in der inneren Shell ausgewertet wird. Keine Garantie.
Ilkkachu
@ilkkachu Diese Version schlägt fehl, wenn das Argument ein einfaches Anführungszeichen enthält. (Aber nur in diesem Fall, was die Validierung viel einfacher macht als bei Verwendung von doppelten Anführungszeichen.) Es ist unmöglich, eine beliebige Zeichenfolge direkt zu interpolieren. Sie müssen entweder die Saite massieren (z. B. alle 'durch ersetzen '\'') oder sie durch einen anderen Kanal wie eine Umgebungsvariable leiten (was ich hier tun würde :) file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"'. Es stellt sich heraus, dass der Dateiname ein virtueller Hostname sein soll, daher wäre es auch hier in Ordnung, alle Sonderzeichen abzulehnen.
Gilles 'SO - hör auf böse zu sein'