Wie kann ich eine Variablenerweiterung in einer Zeichenfolge zitieren, um eine Wortteilung zu vermeiden?

9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

Wie kann ich in einem solchen Fall zitieren $myvar, um eine Wortteilung aufgrund der Leerzeichen im Wert von zu vermeiden myvar?

Tim
quelle
4
Egal wie Sie dies lösen, es wird immer noch nicht viel bewirken. Das hat cdnur Wirkung innerhalb der bash -cSchale.
Kusalananda
Ich gebe nur ein minimales Beispiel für die Frage, es ist keine funktionierende Lösung für mein eigentliches Problem, nämlich unix.stackexchange.com/a/269080/674 plus das in der Befehlszeichenfolge, für die sudo bash -cich eine variable Erweiterung habe Ein Pfadname, der Leerzeichen enthalten kann.
Tim

Antworten:

13

Es gibt kein Wort Spaltung (wie in dem Merkmal , dass Splits Variablen auf nicht notierte Erweiterungen) in diesem Code als $myvarnicht unquoted wird.

Es gibt jedoch eine Sicherheitsanfälligkeit bezüglich Befehlsinjektion, die $myvarerweitert wird, bevor sie an übergeben wird bash. Sein Inhalt wird also als Bash-Code interpretiert!

Leerzeichen dort führen dazu, dass mehrere Argumente übergeben werden cd, nicht aufgrund der Wortaufteilung , sondern weil sie in der Shell-Syntax als mehrere Token analysiert werden. Mit einem Wert von bye;rebootwird das neu gestartet! ¹

Hier möchten Sie:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(wo Sie den Inhalt von $myvarals erstes Argument dieses Inline-Skripts übergeben; beachten Sie, wie beide $myvarund $1für ihre jeweilige Shell zitiert wurden, um das Aufteilen von IFS-Wörtern (und das Globbing) zu verhindern).

Oder:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(wo Sie den Inhalt $myvareiner Umgebungsvariablen übergeben).

Natürlich werden Sie nichts Nützliches erreichen, wenn Sie nur cd in diesem Inline-Skript ausgeführt werden (außer zu prüfen, ob dies möglich rootist cd). Vermutlich möchten Sie das Skript cddort und dann dort etwas anderes tun wie:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

Wenn die Absicht war , zu verwenden , sudoum die Lage sein , cdin ein Verzeichnis , das Sie sonst keinen Zugang zu, dann das kann nicht wirklich funktionieren.

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

startet ein interaktives bashmit seinem aktuellen Verzeichnis in $myvar. Aber diese Shell wird als ausgeführt root.

Du könntest es tun:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

Um eine nicht privilegierte Interaktion bashmit dem aktuellen Verzeichnis zu erhalten $myvar, aber wenn Sie überhaupt nicht die Berechtigung hatten, cdin dieses Verzeichnis zu gelangen, können Sie in diesem Verzeichnis nichts tun, selbst wenn es Ihr aktuelles Arbeitsverzeichnis ist.

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

Eine Ausnahme wäre, wenn Sie über eine Suchberechtigung für das Verzeichnis selbst verfügen, jedoch nicht für eine der Verzeichniskomponenten seines Pfads:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

¹ Genau genommen würde es für Werte $myvarwie $(seq 10)(wörtlich) natürlich zu einer Wortaufteilung kommen, wenn diese Befehlssubstitution durch die bash Shell als erweitert wirdroot

Stéphane Chazelas
quelle
4

Es gibt neue (Bash 4.4) Zitate, mit denen Sie direkter tun können, was Sie wollen. Abhängig vom größeren Kontext ist die Verwendung einer der anderen Techniken möglicherweise besser, funktioniert jedoch in diesem begrenzten Kontext.

sudo bash -c "cd ${myvar@Q}; pwd"
Seth Robertson
quelle
4

Wenn Sie dem Inhalt von nicht vertrauen können $myvar, besteht der einzig vernünftige Ansatz darin, GNU printfzu verwenden, um eine maskierte Version davon zu erstellen:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

Wie die printfManpage sagt:

%q

ARGUMENT wird in einem Format gedruckt, das als Shell-Eingabe wiederverwendet werden kann, wobei nicht druckbare Zeichen mit der vorgeschlagenen POSIX- $''Syntax vermieden werden .

Toby Speight
quelle
Siehe meine Antwort für eine tragbare Version davon.
R .. GitHub STOP HELPING ICE
GNU printfwird dort nur auf GNU-Systemen aufgerufen und wenn dies $(printf '%q' "$myvar")in einer Shell ausgeführt wird, in der printfes nicht eingebaut ist. Die meisten Muscheln sind printfheutzutage eingebaut. Nicht alle unterstützen ein %qobwohl und wenn sie dies tun, würden sie etwas in einem Format zitieren, das von der entsprechenden Shell unterstützt wird, was nicht unbedingt das gleiche sein muss wie das, das von verstanden wird bash. Tatsächlich zitiert bashs printfdie Dinge für einige Werte $myvarin einigen Regionen nicht richtig für sich .
Stéphane Chazelas
Mit bash-4.3zumindest, das ist immer noch ein Befehl Injection - Schwachstelle mit myvar=$'\xa3``reboot`\xa3`' in einem zh_HK.big5hkscs zum Beispiel locale.
Stéphane Chazelas
3

Wie andere angemerkt haben, ist Ihr cdBefehl zunächst nutzlos, da er im Kontext einer Shell ausgeführt wird, die sofort beendet wird. Aber im Allgemeinen macht die Frage Sinn. Was Sie brauchen, ist eine Möglichkeit, eine beliebige Zeichenfolge in Shell zu zitieren , damit sie in einem Kontext verwendet werden kann, in dem sie als Zeichenfolge in Shell-Anführungszeichen interpretiert wird. Ich habe ein Beispiel dafür auf meiner Sh-Tricks- Seite:

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

Mit dieser Funktion können Sie dann Folgendes tun:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
3

Was Stéphane Chazelas vorschlägt, ist definitiv der beste Weg, dies zu tun. Ich werde nur eine Antwort geben, wie man mit der Parametererweiterungssyntax sicher zitiert, anstatt einen sedUnterprozess zu verwenden, wie in der Antwort von R ..

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

Die Ausgabe dieses Skripts lautet:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar
JoL
quelle
1

Ja, es gibt Wortspaltung.
Technisch gesehen wird die Befehlszeile beim Bash-Parsen der Zeile aufgeteilt.

Lassen Sie uns zuerst richtig zitieren:

Die einfachste (und unsicherste) Lösung, um den Befehl so auszuführen, wie Sie es meiner Meinung nach wollen, ist:

bash -c "cd \"$myvar\""

Lassen Sie uns bestätigen, was passiert (vorausgesetzt myvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

Wie Sie sehen können, wurde die Pfadzeichenfolge auf Leerzeichen aufgeteilt. Und:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

Ist nicht geteilt.

Der Befehl (wie geschrieben) ist jedoch unsicher. Bessere Verwendung:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

Dadurch wird die CD vor Dateien geschützt, die mit einem Bindestrich (-) beginnen oder Links folgen, die zu Problemen führen können.

Dies funktioniert, wenn Sie darauf vertrauen können, dass der String $myvarein Pfad ist.
"Code-Injection" ist jedoch weiterhin möglich, da Sie "einen String ausführen":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

Sie benötigen ein stärkeres Zitat der Zeichenfolge, wie:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

Wie Sie oben sehen können, wurde der Wert nicht interpretiert, sondern nur als verwendet "$1'.

Jetzt können wir sudo (sicher) verwenden:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

Wenn es mehrere Argumente gibt, können Sie Folgendes verwenden:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"
Isaac
quelle
-2

Einfache Anführungszeichen funktionieren hier nicht, da Ihre Variable dann nicht erweitert wird. Wenn Sie sicher sind, dass die Variable für Ihren Pfad bereinigt ist, besteht die einfachste Lösung darin, einfach Anführungszeichen um die resultierende Erweiterung hinzuzufügen, sobald sie sich in der neuen Bash-Shell befindet:

sudo bash -c "cd \"$myvar\""
cunninghamp3
quelle
2
Immer noch eine Sicherheitsanfälligkeit $myvar$(reboot)"; reboot; #
bezüglich
1
Die Verwendung sudo bash -c "cd -P -- '$myvar'"würde das Problem auf nur Werte beschränken $myvar , die einfache Anführungszeichen enthalten, die leichter zu bereinigen wären.
Stéphane Chazelas