単語の分割を避けるために、文字列内の変数展開を引用するにはどうすればよいですか?


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

そのような場合、$myvar値の空白のために単語の分割を避けるためにどのように引用できmyvarますか?


4
これをどのように解決しても、それでもあまり効果はありません。cd内側のみ効果があるbash -cシェルを。
クサラナンダ

私はそれが私の本当の問題は、営業ソリューションではありません、唯一の問題のためにのみ最小限の例を与えているunix.stackexchange.com/a/269080/674コマンド文字列中に与えられていることに加えてsudo bash -c、私は、変数の拡張を持っています空白を含む可能性のあるパス名。
Tim、

回答:


13

何もありません単語の分割は、ように、そのコードに(引用符で囲まれていない拡張時に変数を分割機能のように)$myvar引用符で囲まれていないではありません。

ただし、$myvarに渡される前に拡張されるコマンドインジェクションの脆弱性がありbashます。したがって、その内容はbashコードとして解釈されます!

そこにスペースがいくつかの引数が渡されることになりますcdないための、単語の分割が、彼らはシェルの構文にはいくつかのトークンとして解析されますので。の値を指定bye;rebootすると、再起動します!¹

ここでは、次のようにします。

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

$myvarそのインラインスクリプトの最初の引数としての内容を渡す場合、IFSの単語の分割(およびグロビング)を防ぐために、それぞれのシェルで両方$myvar$1が引用されていることに注意してください)。

または:

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

$myvar環境変数での内容を渡す場所)。

もちろん、インラインスクリプトでのみ 実行cdしてrootcd、そこに実行できるかどうかを確認する以外に、有用なことは何もありません。おそらく、そのスクリプトをcdそこに置いてから、次のような他のことを行います。

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

を使用sudocdて、他の方法ではアクセスできないディレクトリにアクセスできるようにするつもりなら、それは実際には機能しません。

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

bash現在のディレクトリを使用してインタラクティブを開始し$myvarます。しかし、そのシェルはとして実行されrootます。

あなたがすることができます:

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

bash現在のディレクトリが$myvarである非特権インタラクティブを取得するにcdは、最初にそのディレクトリへのアクセス権を持っていなかった場合、現在の作業ディレクトリであっても、そのディレクトリで何かを行うことはできません。

$ 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

例外は、ディレクトリ自体に対する検索権限はあるが、パスのディレクトリコンポーネントの1つに対する権限がない場合です。

$ 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

¹厳密に言えば、$myvarlike $(seq 10)(文字通り)の値の場合、次のようにbash 開始されたシェルによるコマンド置換の展開時に、もちろん単語分割があります。root


4

新しい(bash 4.4)引用マジックがあり、より直接的にやりたいことができます。より大きなコンテキストによっては、他の手法のいずれかを使用する方が良い場合がありますが、この限られたコンテキストで機能します。

sudo bash -c "cd ${myvar@Q}; pwd"

4

の内容が信頼できない場合は$myvar、GNU printfを使用してエスケープバージョンを作成するのが妥当なアプローチです。

#!/bin/bash

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

同様にprintfmanページは言います:

%q

ARGUMENTは、シェル入力として再利用できる形式で出力され、提案されたPOSIX $''構文を使用して出力不可能な文字をエスケープします。


これのポータブルバージョンについては、私の回答を参照してください。
R .. GitHub ICE HELPING ICEの停止

GNU printfは、GNUシステム上でのみ呼び出され、それ$(printf '%q' "$myvar")printf組み込みでないシェルで実行される場合。printf現在、ほとんどのシェルにビルトインがあります。%qただし、すべてがサポートしているわけではなく、サポートしている場合は、対応するシェルでサポートされている形式で引用されbashます。実際、一部のロケールの一部の値については、bash' printf自体についても正しく引用できません$myvar
ステファンChazelas

ではbash-4.3、少なくとも、それはまだでコマンドインジェクションの脆弱性だmyvar=$'\xa3``reboot`\xa3`' インスタンスのロケールzh_HK.big5hkscsインチ
ステファンChazelas

3

まず、他の人が指摘したように、cdコマンドはすぐに終了するシェルのコンテキストで発生するため、役に立たない。しかし、一般的にこの質問は理にかなっています。必要なのは、任意の文字列シェルクォートして、シェルクォートされた文字列として解釈されるコンテキストで使用できるようにする方法です。私のshトリックページにこの例があります。

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

この関数を使用すると、次のことができます。

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"

3

StéphaneChazelasの提案は、間違いなくこれを行うための最良の方法です。sedR ..の回答のように、サブプロセスを使用するのではなく、パラメーター展開構文で安全に引用する方法についての回答を提供します。

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

そのスクリプトの出力は次のとおりです。

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

1

はい、単語分割があります。
まあ、技術的には、bashで行を解析する際のコマンドライン分割です。

最初に引用を正しくしましょう:

あなたが望んでいるとおりにコマンドを機能させるための最も簡単な(そして安全でない)ソリューションは次のとおりです。

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

何が起こるか確認してみましょう(を想定myvar="/path to/my directory"):

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

ご覧のとおり、パス文字列はスペースで区切られています。そして:

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

分割されていません。

ただし、コマンドは(書かれているとおり)安全ではありません。より良い使用:

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

これにより、ダッシュ(-)で始まるファイルまたは問題を引き起こす可能性のあるリンクをたどるファイルからcdが保護されます。

文字列$myvarがパスであると信頼できる場合、これは機能します。
ただし、「文字列を実行」しているため、「コードインジェクション」は引き続き可能です。

$ 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

次のように、文字列をより強く引用する必要があります。

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

上記のように、値は解釈されず、そのままとして使用されました"$1'

これで(安全に)sudoを使用できます。

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

複数の引数がある場合は、次を使用できます。

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

-2

ここでは単一引用符は機能しません。それ以降、変数は展開されません。パスの変数が無害化されていると確信している場合、最も簡単な解決策は、新しいbashシェルに配置されたら、結果の展開を単に引用符で囲むことです。

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

2
それでもコマンドインジェクションの脆弱性、の値のために好きな$myvarように$(reboot)"; reboot; #
ステファンChazelas

1
を使用sudo bash -c "cd -P -- '$myvar'"すると、問題を$myvar 一重引用符が含まれている値のみに制限し、サニタイズが容易になります。
ステファンChazelas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.