Come posso assicurarmi che una variabile contenga solo un nome file valido?


8

Dato lo script seguente, come posso assicurarmi che l'argomento contenga solo un nome file valido all'interno /home/charlesingalls/e non un percorso ( ../home/carolineingalls/) o un carattere jolly, ecc.?

Voglio solo che lo script sia in grado di eliminare un singolo file dalla directory specificata. Questo script verrà eseguito come utente privilegiato.

#!/bin/bash

rm -f /home/charlesingalls/"$1"

2
Se non vuoi supportare "foo / bar", controlla che non contenga /. I caratteri jolly non vengono interpretati tra virgolette.
Casuale 832,

3
Se stai eliminando un file , non utilizzarlo -rcon rm. rm -rè per la cancellazione ricorsiva di una directory e di tutti i file e le directory sottostanti. È utile solo quando si eliminano le directory. Più in generale, non culto delle merci. vale a dire non copiare cose che sembrano utili nella riga di comando o nello script senza capire cosa fanno o come funzionano. Gli dei dell'aereo che portano il carico magico possono arrabbiarsi ed eliminare tutti i tuoi file.
Cas

Un buon punto per quanto riguarda la bandiera -r: capisco il suo uso, ma non stavo pensando chiaramente.
Aaron Cicali,

Risposte:


7

Se vuoi solo eliminare un file /home/charlesingalls(e non un file in una sottodirectory), allora è facile: controlla che l'argomento non contenga a /.

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

Ciò viene eseguito rmanche se l'argomento è .o ..o vuoto, ma in tal caso non rmriuscirà a eliminare una directory in modo innocuo.

I caratteri jolly non sono rilevanti qui poiché non viene eseguita alcuna espansione di caratteri jolly.

Questo è sicuro anche in presenza di collegamenti simbolici: se il file è un collegamento simbolico, il collegamento simbolico (che è in /home/charlesingalls) viene rimosso e la destinazione di quel collegamento non viene influenzata.

Si noti che ciò presuppone che /home/charlesingallsnon può essere spostato o modificato. Questo dovrebbe andare bene se la directory è codificata nello script, ma se è determinata dalle variabili, la determinazione potrebbe non essere più valida al momento rmdell'esecuzione del comando.

Sulla base delle informazioni aggiuntive secondo cui l'argomento è un nome host virtuale, è necessario eseguire la whitelist anziché la blacklist: verificare che il nome sia un nome host virtuale sensibile, anziché semplicemente vietare le barre. Controllo che il nome inizi con una lettera o una cifra minuscola e che non contenga caratteri diversi da lettere minuscole, cifre, punti e trattini.

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

In questo caso, i file sono file di configurazione del server Web che sono stati generati da un processo precedente. Hanno tutti lo stesso livello di importanza (ognuno costituisce un host virtuale). Tuttavia, la directory in cui si trovano è adiacente a directory simili e sono tutte presenti in una directory di proprietà del server Web. Questo particolare script ha lo scopo di consentire la cancellazione delle configurazioni dell'host virtuale solo in una determinata directory.
Aaron Cicali,

questo approccio ha senso per te a condizione che utilizzi questo caso? Apprezzo la tua esperienza e ammetto che ci sono sicuramente delle volte in cui "ho portato un bazooka ad uno scontro a fuoco" per automatizzare qualcosa in Linux.
Aaron Cicali,

@AaronCicali Perché lo script può eliminare qualsiasi configurazione dell'host e non solo quella appartenente all'entità che ha effettuato la richiesta originale? Perché il nome host virtuale non è stato convalidato per primo (quindi non conterrebbe alcun carattere speciale)?
Gilles 'SO- smetti di essere malvagio' il

L'entità che effettua la richiesta originale è una GUI che dispone effettivamente dell'autorizzazione per rimuovere qualsiasi host virtuale in quella cartella. Il nome host virtuale viene prima convalidato. Viene da un elenco di host virtuali precedentemente creati (nomi di dominio). Credo che sia compito di questo script garantire che sia il più sicuro possibile senza fare affidamento sulla sicurezza di un'altra parte dell'applicazione. Vale a dire, che può eliminare solo i file all'interno di questa directory specifica. Dovrà inoltre eseguire alcuni lavori di pulizia aggiuntivi.
Aaron Cicali,

@AaronCicali Ok, come ulteriore controllo della sanità mentale ha senso. In questo caso dovresti inserire la whitelist: accetta solo nomi che sembrano nomi host plausibili. Se non consenti i sottodomini, potresti addirittura vietare.
Gilles 'SO- smetti di essere malvagio'

10

Questa risposta presuppone che $1sia consentito includere le sottodirectory. Se sei interessato al caso più semplice in cui $1dovrebbe esserci un semplice nome di directory, vedi una delle altre risposte.


I caratteri jolly non vengono espansi tra virgolette doppie. Dato che $1è tra virgolette, i caratteri jolly non sono un problema.

Entrambi ../e i collegamenti simbolici possono oscurare la posizione reale di un file. Di seguito sono mostrati i test per determinare se il file si trova davvero, non solo apparentemente, nel percorso desiderato.

Sistemi più recenti: utilizzo realpath

Per sapere se il file è realmente se il file è davvero sotto /home/charlesingalls/o no, puoi usare realpath:

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

Quanto sopra viene eseguito exit 1se il file specificato da si $1trova in un punto diverso dalla directory /home/charlesingalls/. realpathcanonicalizza l'intero percorso, eliminando sia i symlink che i ../.

realpath fa parte dei coreutils GNU e dovrebbe essere disponibile su qualsiasi sistema Linux.

realpathrichiede GNU coreutils 8.15 (gennaio 2012) o superiore .

Esempi

Per dimostrare come segue realpath ../per determinare la posizione reale di un file (ad esempio, l' -qopzione grep è omessa in modo che sia visibile l'output effettivo di grep):

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

Per dimostrare come segue i collegamenti simbolici:

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

Sistemi meno recenti: utilizzo readlink -e

readlinkè anche in grado di cononicalizzare un percorso, seguendo entrambi i link simbolici e ../:

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

Utilizzando gli stessi file di esempio:

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

Oltre ad essere disponibili su sistemi GNU precedenti, le versioni di readlinksono disponibili su BSD.


Il mio Ubuntu 14.04 coreutilsnon harealpath
heemayl

1
Questo sembra essere più di quello che sto cercando, ma sfortunatamente il mio server Centos 6.5 non ha percorso reale. Non sono in grado di installarlo. Googling rivela menzioni del realpath superceding "readlink -f", ma devo ancora farlo funzionare.
Aaron Cicali,

1
I sottotitoli menzionano -f("devono esistere tutti tranne l'ultimo componente") e gli esempi usano -e("devono esistere tutti i componenti"), il che è un po 'confuso.
Isanae,

2
@AaronCicali Questa non è sicuramente la risposta di cui hai bisogno, perché è pericolosamente sbagliata. Non è necessario risolvere i collegamenti simbolici . Il target del collegamento simbolico può variare tra il momento in cui lo controlli e il momento in cui lo usi. (È una categoria ben nota di errori di progettazione ). Inoltre, non ha senso risolvere i collegamenti simbolici poiché rmagisce sull'argomento stesso, non sul suo obiettivo.
Gilles 'SO- smetti di essere malvagio' il

1
Suggerimento: utilizzare grep -qper evitare grepdi generare linee corrispondenti. Ottieni ancora lo stato di uscita così &&e ||funziona ancora esattamente come sei abituato.
un CVn

2

Se si desidera vietare completamente i percorsi, il modo più semplice è verificare se la variabile contiene una barra ( /). In bash:

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

Questo bloccherà comunque tutti i percorsi, incluso foo/bar. Potresti ..invece provare , ma ciò lascerebbe la possibilità di collegamenti simbolici che puntano a directory esterne al percorso di destinazione.

Se vuoi solo consentire l'eliminazione di un singolo file, non penso che dovresti usare rm -r.


Inoltre, a seconda di ciò che si sta facendo, è possibile utilizzare le autorizzazioni dei file di sistema per consentire solo l'eliminazione dei file che l'utente può eliminare da solo. Qualcosa come questo:

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

Anche se come ha commentato @Gilles, questo ha un problema di quotazione: fallirà se $1contiene una singola citazione, quindi la variabile dovrebbe essere testata per quella prima (con ad esempio if [[ "$1" = *\'* ]] ; then fail...o piuttosto inserendo nella whitelist un set sensibile di caratteri), oppure il nome del file passato una variabile d'ambiente con es

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

Un buon punto per quanto riguarda la bandiera -r, che in realtà non dovrebbe essere lì. Rimozione ora ...
Aaron Cicali

Nota che il tuo sucomando è rotto perché la quotazione è sbagliata. Stai eseguendo un comando con l'argomento interpolato come frammento di shell. Ad esempio, se l'argomento è $(touch foo)allora il tuo codice viene eseguito touch foo.
Gilles 'SO- smetti di essere malvagio' il

@Gilles, merda, sapevo che doveva esserci qualcosa di sbagliato. la citazione nidificata non è il mio amico preferito. Penso che la versione corrente funzioni meglio, le virgolette doppie valuteranno la variabile nella shell esterna e le virgolette singole impediranno che venga valutata nella shell interna. Nessuna garanzia.
ilkkachu,

@ilkkachu Quella versione fallisce se l'argomento contiene una singola citazione. (Ma solo in quel caso, il che rende molto più semplice la convalida rispetto all'uso di virgolette doppie.) È impossibile interpolare direttamente una stringa arbitraria. Devi massaggiare la stringa (ad es. Sostituire tutto 'con '\'') o passarla attraverso un altro canale come una variabile d'ambiente (che è quello che farei qui:) file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"'. Si scopre che il nome del file dovrebbe essere un nome host virtuale, quindi rifiutare tutti i caratteri speciali andrebbe bene anche qui.
Gilles 'SO- smetti di essere malvagio' il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.