unix.stackexchange.comをしばらくフォローしている場合は、
リストコンテキストで変数を引用符で囲まずに
残すことを
知っているはずです( echo $ var
のように) )Bourne / POSIXでは
シェル(zshは例外)には非常に特別な意味があり、
非常に正当な理由がない限り、実行しないでください。
ここでは、いくつかのQ&Aで詳細に説明されています(例:シェルスクリプトが空白やその他の特殊文字でチョークするのはなぜですか?、二重引用符が必要なのはいつですか?、シェル変数の拡張とglobの効果とその分割、引用符vs引用符で囲まれていない文字列展開 )
これは、70年代後半のボーンシェルの最初のリリース以来のケースであり、Kornシェルによって変更されていません
(David Kornの最大の後悔の1つ
(質問#7 ))または bash
これは主に
Kornシェルをコピーしたものであり、それがPOSIX/Unixによって指定された方法です。
現在、ここにはまだ多くの回答があり、
変数が引用されていない
公開されているシェルコードもあります。あなたは人々が今までに
学んだだろうと思っていたでしょう。
私の経験では、主に3つのタイプの人々が
変数の引用を省略しています:
-
初心者。確かに、それは
完全に直感的でない構文であるため、これらは許されます。そして、このサイトでの私たちの役割は、
彼らを教育することです。 -
忘れられた人々。
-
何度も叩いても確信が持てない人、
ボーンシェルの作者が
すべての変数を引用するつもりはなかったと思う
。
この種の行動に関連するリスクを明らかにすれば、彼らを納得させることができるかもしれません。
変数の引用を忘れた場合に発生する可能性があるよりも、最悪の事態は何ですか。本当に 悪いですか?
ここで話しているのはどのような脆弱性ですか?
どのような状況で問題になる可能性がありますか?
承認された回答:
前文
まず、問題に対処するのは正しい方法ではないと思います。
これは、「人を殺してはいけません。
そうしないと刑務所に行くからです」と言っているようなものです。 「。
同様に、変数を引用しないでください。そうしないと、
セキュリティの脆弱性が発生することになります。
変数を引用するのは間違っているためです(ただし、刑務所への恐怖が役立つ場合は、そうしないでください)。
電車に飛び乗ったばかりの人のための簡単な要約。
ほとんどのシェルでは、変数展開を引用符で囲まずに残します(ただし、
それ(およびこの回答の残りの部分)はコマンド
置換にも適用されます( `...`
または$(...)
)および算術展開( $((...))
または$[...]
))非常に特別な
意味があります。それを説明する最良の方法は、
ある種の暗黙的なsplit + globを呼び出すようなものです。 オペレーター¹。
cmd $var
別の言語では、次のように記述されます:
cmd(glob(split($var)))
$ var
最初に、 $ IFS
を含む複雑な
ルールに従って単語のリストに分割されます 特別なパラメータ(分割 一部)
そして、その分割の結果として生じる各単語は、
パターンと見なされます。 これは、それに一致するファイルのリストに展開されます
( glob 一部)。
例として、 $ var
の場合 *。txt、/ var/*。xml
が含まれています および$IFS
、
が含まれています 、 cmd
いくつかの引数を使用して呼び出されます。
最初の引数はcmd
です。 次はtxt
です 現在のディレクトリとxml
内のファイル / var
内のファイル 。
cmd
を呼び出したい場合 2つのリテラル引数cmd
だけで および*。txt、/ var/*。xml
、あなたは書くでしょう:
cmd "$var"
これは、他のより身近な言語になります:
cmd($var)
シェルの脆弱性とはどういう意味ですか ?
結局のところ、シェルスクリプトはセキュリティに敏感なコンテキストで使用されるべきではないことが昔から知られていました。
確かに、変数を引用符で囲まないままにしておくことはバグですが、それはできません。
それだけの害はありますか?
まあ、誰もがシェルスクリプトをWeb CGIに使用してはいけないと言うだろうという事実にもかかわらず、またはありがたいことに
現在、ほとんどのシステムはsetuid/setgidシェルスクリプトを許可していません。
1つshellshock(2014年9月に話題になったリモートで悪用可能なbashバグ
)が明らかにしたことは、シェルがおそらく使用されるべきではない場所で
まだ広く使用されていることです:
CGI、DHCPクライアントsudoersコマンドのフックスクリプト
によって呼び出されます ( asでない場合 )setuidコマンド…
時々無意識のうちに。たとえば、 system('cmd $ PATH_INFO')
php
で / perl
/ python
CGIスクリプトは、シェルを呼び出してそのコマンドラインを解釈します(
cmd
という事実は言うまでもありません。 それ自体がシェルスクリプトである可能性があり、その
作成者は、CGIから呼び出されることを予期していなかった可能性があります。
特権のエスカレーションへのパスがある場合、つまり誰か(彼を攻撃者と呼びましょう)の場合に脆弱性があります )
彼が意図していないことをすることができます。
常にそれは攻撃者を意味します データを提供すると、そのデータは
特権ユーザー/プロセスによって処理され、
バグが原因で、ほとんどの場合、
実行してはいけないことを
実行します。
基本的に、バグのあるコードが攻撃者の制御下でデータを処理するときに問題が発生します。
。
さて、そのデータがどこにあるかは必ずしも明らかではありません
から来る可能性があり、コードが信頼できないデータを処理するようになるかどうかを判断するのは難しいことがよくあります。
変数に関する限り、CGIスクリプトの場合、
非常に明白ですが、データはCGI GET / POSTパラメータであり、
Cookie、パス、ホスト…パラメータなどです。
setuidスクリプト(
別のユーザーによって呼び出されたときに1人のユーザーとして実行される)の場合、それは引数または環境変数です。
もう1つの非常に一般的なベクトルは、ファイル名です。ディレクトリから
ファイルリストを取得している場合は、攻撃者によって
ファイルがそこに植えられている可能性があります 。
その点で、対話型シェルのプロンプトでも、
脆弱になる可能性があります( / tmp
内のファイルを処理する場合)。 または〜/ tmp
たとえば)。
〜/ .bashrc
でさえ 脆弱である可能性があります(たとえば、 bash
ssh
を介して呼び出されたときに解釈します ForcedCommand
を実行するには git
のように
クライアントの制御下にあるいくつかの変数を使用したサーバー展開)。
現在、信頼できないデータを処理するためにスクリプトを直接呼び出すことはできませんが、
別のコマンドで呼び出すことができます。または、
間違ったコードが、スクリプトにコピーアンドペーストされる可能性があります(3
年後または同僚の1人によって)。
特に重要の1つの場所
コードのコピーがどこにあるかわからないため、Q&Aサイトに回答があります。
ビジネスに至るまで。どれくらい悪いですか?
変数(またはコマンド置換)を引用符で囲まないままにしておくことは、シェルコードに関連する
セキュリティの脆弱性の最大の原因です。これらのバグは
脆弱性につながることが多いためですが、引用符で囲まれていない変数がよく見られるためです。
実際、シェルコードの脆弱性を探すとき、
最初に行うことは、引用符で囲まれていない変数を探すことです。
簡単に見つけることができ、多くの場合、適切な候補であり、一般的に
攻撃者が制御するデータにさかのぼることができます。
引用符で囲まれていない変数が
脆弱性に変わる可能性のある方法は無数にあります。ここでは、いくつかの一般的な傾向について説明します。
情報開示
split が原因で、ほとんどの人は引用符で囲まれていない
変数に関連するバグにぶつかります。 一部(たとえば、最近ではファイルの名前にスペースが含まれるのが一般的であり、スペースはIFSのデフォルト値になっています)。多くの人がグロブを見落とします 部。 グロブ 一部は少なくとも分割と同じくらい危険です 一部。
消毒されていない外部入力で行われるグロブは、
攻撃者を意味します 任意のディレクトリのコンテンツを読み取らせることができます。
で:
echo You entered: $unsanitised_external_input
$ unsanitised_external_input
の場合 / *
が含まれています 、つまり
攻撃者 /
の内容を見ることができます 。大きな問題ではない。
/ home / *
を使用すると、さらに面白くなります これにより、マシン上の
ユーザー名のリスト/ tmp / *
が表示されます。 、 /home/*/.forward
他の危険な慣行のヒントについては、 / etc / rc * / *
有効な
サービスの場合…個別に名前を付ける必要はありません。 / * / * / * / * / * / * ...
の値 ファイルシステム全体が一覧表示されます。
サービス拒否の脆弱性。
前のケースを少しやりすぎて、DoSがあります。
実際、サニタイズされていない
入力を持つリストコンテキスト内の引用符で囲まれていない変数は、少なくとも DoSの脆弱性。
熟練したシェルスクリプターでさえ、一般的に次のようなものを引用するのを忘れます:
#! /bin/sh -
: ${QUERYSTRING=$1}
:コード> no-opコマンドです。何がうまくいかない可能性がありますか?
これは、 $ 1
を割り当てることを意味します $ QUERYSTRING
へ $ QUERYSTRING
の場合 設定されていませんでした。これは、CGIスクリプトを
コマンドラインからも呼び出し可能にする簡単な方法です。
その$QUERYSTRING
ただし、まだ拡張されており、
引用されていないため、 split + glob 演算子が呼び出されます。
現在、
拡張するのに特に費用がかかるグロブがいくつかあります。 /* / * / * / *
1つは、最大4レベル下のディレクトリを
リストすることを意味するので十分に悪いです。ディスクとCPUのアクティビティに加えて、
これは、数万のファイルパスを保存することを意味します
(ここでは、最小のサーバーVMで40k、そのうちの10kはディレクトリです)。
今/* / * / * / * / .. / .. / .. / .. / * / * / * / *
40kx10kおよび/* / * / * / * / .. / .. / .. / .. / * / * / * / * / .. / .. / .. / ../*を意味します/ * / * / *
最も強力なマシンでさえもひざまずくには十分です。
自分で試してみてください(ただし、マシンが
クラッシュまたはハングする準備をしてください):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
もちろん、コードが次の場合:
echo $QUERYSTRING > /some/file
次に、ディスクをいっぱいにすることができます。
シェルでGoogle検索を実行するだけです
cgiまたはbash
cgiまたはksh
cgi
シェルでCGIを作成する方法を示すページがいくつかあります。
パラメータを処理するものの半分が脆弱であることに注意してください。
DavidKornの
自分の
ものでさえ脆弱です(Cookieの処理を見てください)。
任意のコード実行の脆弱性まで
攻撃者の場合、任意のコードの実行は最悪のタイプの脆弱性です。
任意のコマンドを実行できます。
彼が実行できることには制限がありません。
これは通常、分割 それらにつながる部分。その
分割により、1つだけが予想される場合に、コマンドに渡されるいくつかの引数が
発生します。それらの最初のものは予想されるコンテキストで
使用されますが、他のものは異なるコンテキストで使用されるため
、異なる解釈が行われる可能性があります。例を挙げてみましょう:
awk -v foo=$external_input '$2 == foo'
ここでの目的は、 $ external_input
のコンテンツを割り当てることでした。 foo
へのシェル変数 awk
変数。
今:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
$ external_input
の分割の結果として生じる2番目の単語 foo
に割り当てられていません ただし、 awk
と見なされます コード(ここでは
任意のコマンドを実行します: uname
。
これは、他の
コマンド( awk
)を実行できるコマンドでは特に問題になります。 、 env
、 sed
(GNU one)、 perl
、 find
…)特に
GNUバリアント(引数の後にオプションを受け入れる)を使用します。
場合によっては、
ksh
のような他のコマンドを実行できるとは思わないでしょう。 、 bash
またはzsh
の[コード> または
printf
…
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
x -o yes
というディレクトリを作成すると 、その後、テストは
陽性になります。これは、私たちが評価している
条件式とはまったく異なるためです。
さらに悪いことに、 x -a a [0 $(uname>&2)] -gt 1
というファイルを作成すると 、
少なくともすべてのksh実装( sh
を含む) uname
を実行するほとんどの商用Unicesおよび一部のBSDの) これらのシェルは、[
の
数値比較演算子で算術評価を実行するためです。 コマンド。
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
bash
と同じ x -a -v a [0 $(uname>&2)]
のようなファイル名の場合 。
もちろん、恣意的に実行できない場合は、攻撃者
より少ないダメージで解決するかもしれません(これは恣意的な実行を得るのに役立つかもしれません
)。ファイルを書き込んだり、権限や所有権を変更したり、主効果や副作用をもたらしたりする可能性のあるコマンドはすべて悪用される可能性があります。
あらゆる種類のことがファイル名で実行できます。
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
そして、あなたは ..
を作ることになります 書き込み可能(GNU chmod
で再帰的に 。
/ tmp
のような公的に書き込み可能な領域でファイルの自動処理を行うスクリプト 非常に注意深く書かれるべきです。
[$#-gt 1]
はどうですか
それは私が腹立たしいと思うものです。一部の人々は、
特定の拡張が、引用符を省略できるかどうかを判断するのに
問題があるのではないかと考えるのに苦労します。
言っているようなものです。 ねえ、 $#
のようです
split+glob演算子の対象にはなりません。シェルにsplit+globを実行するように依頼しましょう 。
またはバグが発生する可能性が低いという理由だけで、間違ったコードを記述しましょう 。
今、それはどれほどありそうもないですか? OK、 $#
(または $!
、 $?
または
算術置換)には数字(または-
)のみを含めることができます
some²)ので、 glob 一部が出ています。 分割の場合
何かをする部分ですが、必要なのは $ IFS
だけです。 数字を含める(または-
。
一部のシェルでは、 $ IFS
環境から継承される可能性がありますが、
環境が安全でない場合は、とにかくゲームオーバーです。
ここで、次のような関数を作成すると、次のようになります。
my_function() {
[ $# -eq 2 ] || return
...
}
つまり、関数の動作は、呼び出されるコンテキストに
依存するということです。つまり、 $ IFS
それへの入力の1つになります。厳密に言えば、
関数のAPIドキュメントを作成するときは、
次のようになります。
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
また、関数を呼び出すコードでは、 $ IFS
を確認する必要があります。
数字は含まれていません。
これらの2つの二重引用符の文字を入力する気がなかったからです。
さて、その [$#-eq 2]
バグが脆弱になるため、
$ IFS
の値にはどういうわけか必要です
攻撃者の管理下に置かれる 。おそらく、攻撃者が しない限り、通常は
発生しません。 なんとか別のバグを悪用することができました。
しかし、それは前代未聞ではありません。一般的なケースは、
算術式で使用する前にデータをサニタイズするのを忘れた場合です。
一部のシェルで任意のコードが実行される可能性があることはすでに説明しましたが、すべてのシェルで攻撃者が可能になります。 任意の変数に整数値を与えるため。
例:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
そして$1
値(IFS =-1234567890)
、その算術評価
には、IFSと次の[
の設定の副作用があります コマンドが失敗します。これは、引数が多すぎるのチェックを意味します
バイパスされます。
split + glob の場合はどうですか? 演算子は呼び出されませんか?
変数やその他の展開の前後に引用符が必要な別のケースがあります。それがパターンとして使用される場合です。
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
$ a
かどうかをテストしないでください および$b
同じです( zsh
を除く )ただし、 $ a
の場合 $ b
のパターンと一致します 。そして、 $ b
を引用する必要があります 文字列として比較する場合( "$ {a#$ b}"
でも同じです または"${a%$ b}"
または"${a ## * $ b *}"
ここで、 $ b
パターンと見なされない場合は、引用する必要があります。
つまり、 [[$ a =$ b]]
$ a
の場合にtrueを返す場合があります $ b
とは異なります (たとえば、 $ a
の場合 何でも
です および$b
*
です )または、それらが同一である場合(たとえば、両方の $ a
の場合)はfalseを返す場合があります および$b
[a]
です 。
それはセキュリティの脆弱性につながる可能性がありますか?はい、他のバグと同じように。ここで、攻撃者 スクリプトの論理コードフローを変更したり、スクリプトが行っている仮定を破ったりする可能性があります。たとえば、次のようなコードを使用します:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
攻撃者 '[a]''[a]'
を渡すことでチェックをバイパスできます 。
さて、そのパターンマッチングも split + glob も、 オペレーターが適用します。変数を引用符で囲まずに残すことの危険性は何ですか?
私は自分が書いていることを認めなければなりません:
a=$b
case $a in...
そこでは、引用は害にはなりませんが、厳密には必要ではありません。
ただし、このような場合(たとえば、Q&Aの回答で)引用符を省略することの副作用の1つは、初心者に間違ったメッセージを送信する可能性があることです。変数を引用符で囲まなくても大丈夫かもしれません 。
たとえば、 a =$ b
の場合、彼らは考え始めるかもしれません。 OKの場合、 同様になります( export a =$ b
export
の引数にあるため、多くのシェルにはありません。 リストコンテキストでコマンドを実行)または 。 env a =$ b
zsh
はどうですか ?
zsh
それらの設計の厄介さのほとんどを修正しました。 zsh
で (少なくともsh / kshエミュレーションモードでない場合)分割が必要な場合 、または globbing 、またはパターンマッチング 、明示的にリクエストする必要があります: $ =var
分割し、 $〜var
グロブまたは変数のコンテンツをパターンとして処理します。
ただし、分割(グロブではない)は、引用符で囲まれていないコマンド置換時に暗黙的に実行されます( echo $(cmd)
のように)。 。
また、変数を引用しないことによる望ましくない副作用は、空の削除です。 。 zsh
動作は、グロビングを完全に無効にすることで他のシェルで達成できることと似ています( set -f
を使用) )および分割( IFS =''
を使用) )。それでも、:
cmd $var
split + globはありません 、ただし $ var
の場合 1つの空の引数cmd
を受け取る代わりに、は空です 引数はまったくありません。
それはバグを引き起こす可能性があります(明らかな [-n $ var]
のように )。これにより、スクリプトの期待や仮定が破られ、脆弱性が発生する可能性があります。
空の変数により、引数が削除される可能性があるため 、つまり、次の引数が間違ったコンテキストで解釈される可能性があることを意味します。
例として、
printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
$ Attacker_supplied1
の場合 空の場合、 $ Attacker_supplied2
算術式として解釈されます(%d
の場合) )文字列の代わりに(%s
の場合 )および算術式で使用されるサニタイズされていないデータは、zshなどのKornのようなシェルでのコマンドインジェクションの脆弱性です。
$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>
結構ですが:
$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>
uname
任意のコマンド 実行されました。
行うときはどうですか split + globが必要です オペレーター?
はい、それは通常、変数を引用符で囲まないままにしておきたい場合です。ただし、分割を確実に調整する必要があります。 およびglob それを使用する前に正しく演算子。 分割のみが必要な場合 グロブではなく一部 一部(ほとんどの場合)、グロビングを無効にする必要があります( set -o noglob
/ set -f
)そして $ IFS
を修正します 。そうしないと、脆弱性も発生します(前述のDavid KornのCGIの例のように)。
結論
要するに、シェルで変数(またはコマンド置換または
算術展開)を引用符で囲まずに残すことは非常に危険です
特に間違ったコンテキストで行われた場合、
どれがそれらの間違ったコンテキスト。
これが、悪い習慣と見なされる理由の1つです。 。
これまで読んでくれてありがとう。頭を越えても
心配しないでください。誰もが
コードを書く方法でコードを書くことのすべての意味を理解することを期待することはできません。そのため、グッドプラクティスの推奨事項があります 、したがって、
必ずしも理由を理解していなくてもフォローできます。
(それがまだ明らかでない場合は、
セキュリティに敏感なコードをシェルで記述しないでください)。
そして、このサイトの回答に変数を引用してください!