前文
まず、問題に対処する適切な方法ではないと言います。「そうでなければ、刑務所に行くから人を殺してはいけない」と言っているようなものです。
同様に、セキュリティの脆弱性を導入しているため、変数を引用しません。変数を引用するのは間違っていないためです(しかし、刑務所への恐怖が助けになれば、なぜですか)。
電車に乗ったばかりの人のための小さな要約。
ほとんどのシェルでは、変数展開を引用符で囲まずに残します(ただし、この(およびこの回答の残りの部分)はコマンド置換(`...`
または$(...)
)および算術展開($((...))
または$[...]
)にも適用されます)は非常に特別な意味を持ちます。それを記述する最良の方法は、ある種の暗黙的なsplit + glob演算子を呼び出すようなものです¹。
cmd $var
別の言語では、次のように記述されます。
cmd(glob(split($var)))
$var
最初に$IFS
特別なパラメーターを含む複雑なルールに従って単語のリストに分割され(分割部分)、その分割の結果として生じる各単語は、それと一致するファイルのリスト(glob部分)に展開されるパターンと見なされます。
場合の例としては、$var
含まれ*.txt,/var/*.xml
及び$IFS
含まれ,
、cmd
最初の1があること、引数の数で呼び出されることになるcmd
と、次のものがあることtxt
、現在のディレクトリ内のファイルとxml
のファイル/var
。
cmd
2つのリテラル引数cmd
とだけで呼び出したい場合は*.txt,/var/*.xml
、次のように記述します。
cmd "$var"
あなたのもう一つのより親しみやすい言語であるでしょう:
cmd($var)
シェルの脆弱性とはどういう意味ですか?
結局のところ、シェルスクリプトはセキュリティが重要なコンテキストで使用されるべきではないことは、time明期から知られています。確かに、変数を引用符で囲まずに残しておくのはバグですが、それほど害はありませんよね?
誰もがシェルスクリプトをWeb CGIに使用してはならない、またはありがたいことにほとんどのシステムが最近ではsetuid / setgidシェルスクリプトを許可しないと言っているにもかかわらず、shellshock( )2014年9月中の見出しはどこ彼らはおそらくないはずシェルはまだ広く使用されていることである明らかにした:CGIの中で、DHCPクライアントフックスクリプトでは、呼び出されたのsudoersコマンド、中で(ない場合など)のsetuidコマンド...
時々知らずに。たとえばsystem('cmd $PATH_INFO')
、php
/ perl
/ python
CGIスクリプトでは、そのコマンドラインを解釈するためにシェルが呼び出されます(cmd
それ自体がシェルスクリプトであり、作成者がCGIから呼び出されることを予期していなかったという事実は言うまでもありません)。
特権エスカレーションへの道があるとき、つまり誰か(彼を攻撃者と呼びましょう)が彼が意図していないことをすることができるとき、あなたは脆弱性を持っています。
常に、攻撃者がデータを提供することを意味します。そのデータは、ほとんどの場合、バグのために、誤って本来すべきでないことを行う特権ユーザー/プロセスによって処理されています。
基本的に、バグのあるコードが攻撃者の制御下でデータを処理するときに問題が発生します。
現在、そのデータがどこから来たのかが常に明らかであるとは限らず、コードが信頼できないデータを処理できるようになるかどうかを判断するのは難しい場合があります。
変数に関する限り、CGIスクリプトの場合、データはCGI GET / POSTパラメーターと、Cookie、パス、ホストなどのパラメーターです。
setuidスクリプト(別のユーザーによって呼び出されたときに1人のユーザーとして実行される)の場合、それは引数または環境変数です。
別の非常に一般的なベクトルはファイル名です。ディレクトリからファイルリストを取得している場合、攻撃者によってファイルがそこに植えられている可能性があります。
(内のファイルを処理する際にその点でも、対話型シェルのプロンプトで、あなたが影響を受けます/tmp
か~/tmp
例えば)。
でも~/.bashrc
(例えば、脆弱である可能bash
オーバー呼び出されたときにそれを解釈するssh
実行するためForcedCommand
のようにgit
、クライアントの制御下で、いくつかの変数を使用してサーバーの展開を)。
現在、信頼できないデータを処理するためにスクリプトを直接呼び出すことはできませんが、別のコマンドによって呼び出すことができます。または、間違ったコードをコピーするスクリプトにコピーアンドペーストする場合があります(3年後に同僚または同僚の1人によって)。コードのコピーがどこで終わるかわからないので、特に重要なのはQ&Aサイトの回答です。
ビジネスまで。どれくらい悪い?
変数(またはコマンド置換)を引用符で囲まずに残すことは、シェルコードに関連するセキュリティ脆弱性の最も多い原因です。一部には、これらのバグが脆弱性に変換されることが多いだけでなく、引用符で囲まれていない変数を見ることが非常に一般的だからです。
実際、シェルコードの脆弱性を探すとき、最初にすることは、引用符で囲まれていない変数を探すことです。簡単に見つけることができ、多くの場合、適切な候補であり、一般に攻撃者が制御するデータに簡単に戻ります。
引用符で囲まれていない変数が脆弱性になる可能性のある方法は無限にあります。ここでは、いくつかの一般的な傾向を説明します。
情報開示
ほとんどの人は、分割された部分のために、引用されていない変数に関連するバグにぶつかります(たとえば、最近ではファイルの名前にスペースがあり、スペースが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オペレーターが呼び出されます。
現在、拡張するのに特に高価ないくつかのグロブがあります。/*/*/*/*
一つは、それは4つのレベルまでのリストディレクトリの下の手段として十分に悪いです。ディスクとCPUのアクティビティに加えて、それは数万のファイルパス(ここでは最小のサーバーVMで40k、10kのディレクトリ)を格納することを意味します。
今で/*/*/*/*/../../../../*/*/*/*
は40k x 10kを意味し
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
、最も強力なマシンでさえひざまずくのに十分です。
自分で試してみてください(ただし、マシンがクラッシュまたはハングするように準備してください):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
もちろん、コードが次の場合:
echo $QUERYSTRING > /some/file
その後、ディスクをいっぱいにすることができます。
shell cgiまたはbash cgiまたはksh cgiでGoogle検索を実行するだけで、シェルで CGIを作成する方法を示すページがいくつか見つかります。パラメータを処理するものの半分が脆弱であることに注意してください。
David Korn自身のものでさえ
脆弱です(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
の分割の結果として生じる2番目の単語$external_input
は割り当てられませんfoo
が、awk
コードと見なされます(ここでは、任意のコマンドを実行します:)uname
。
それは他のコマンドを実行できるコマンドのために特に問題だ(awk
、env
、sed
(GNU 1)、 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
ほとんどの商用Unicesと一部のBSDを含む)でを呼び出すファイルを作成すると、uname
これらのシェルが[
コマンドの数値比較演算子で算術評価を実行するため実行されます。
$ 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 itを要求しましょう。または、バグがヒットする可能性が低いという理由だけで、間違ったコードを書きましょう。
さて、それはどれほどありそうもないのでしょうか?OK $#
(または$!
、$?
または算術置換)には数字(または-
一部)のみが含まれている可能性があるため、グロブ部分はありません。以下のために、分割部分がが何かをするために、我々が必要とするすべては、ある$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
with value を使用すると(IFS=-1234567890)
、算術評価にIFSの設定の副作用があり、次の[
コマンドが失敗します。これは、引数が多すぎる場合のチェックがバイパスされることを意味します。
どのようなときについてのスプリット+グロブ演算子が呼び出されていませんか?
変数やその他の展開の周りに引用符が必要な別のケースがあります。それがパターンとして使用されるときです。
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
$a
と$b
が同じかどうか(を除くzsh
)をテストせず、if $a
がのパターンに一致する場合$b
。そして、あなたが引用する必要があり$b
ますが、文字列として比較したい場合は(同じもので"${a#$b}"
か"${a%$b}"
またはそれがパターンとして解釈すべきではないかどう引用符で囲む必要があります)。"${a##*$b*}"
$b
それが意味するのは、異なる[[ $a = $b ]]
場合(たとえば、is とisの場合)にtrueを返す場合もあれば、同一の場合(たとえば、両方とandの場合)にfalseを返す場合もあります。$a
$b
$a
anything
$b
*
$a
$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のエミュレーションモードでない少なくともとき)、分割、またはグロブ、またはパターンマッチングを、あなたが明示的に要求する必要があります:$=var
分割すると、$~var
グロブするかのように処理されるべき変数の内容のためにパターン。
ただし、引用符で囲まれていないコマンド置換では、(グロブではなく)分割は暗黙的に行われます(のようにecho $(cmd)
)。
また、変数を引用しない場合の望ましくない副作用として、空の削除があります。zsh
行動は、あなたが(と完全グロブ無効にすることにより、他のシェルで達成できるものに似ているset -f
)と分割(とIFS=''
)。それでも、
cmd $var
split + globはありませんが、$var
空の場合、1つの空の引数を受け取る代わりに、cmd
引数をまったく受け取りません。
それはバグを引き起こす可能性があります(明らかな[ -n $var ]
)。それはスクリプトの期待と仮定を破り、脆弱性を引き起こす可能性がありますが、私は今のところあまりにもフェッチされていない例を思い付くことはできません)。
split + glob演算子が必要な場合はどうですか?
はい、それは通常、変数を引用符で囲まずに残したい場合です。ただし、使用する前に、スプリット演算子とグロブ演算子を正しく調整する必要があります。glob部分ではなく分割部分だけが必要な場合(ほとんどの場合)、グロビング(/ )を無効にして修正する必要があります。そうしないと、脆弱性も発生します(上記のDavid KornのCGIの例のように)。set -o noglob
set -f
$IFS
結論
要するに、シェルで変数(またはコマンド置換や算術展開)を引用符で囲まないままにすることは、特に間違ったコンテキストで行われた場合、非常に危険な場合があります。
それが悪い習慣とみなされる理由の一つです。
これまで読んでくれてありがとう。それがあなたの頭の上に行く場合、心配しないでください。誰もがコードを書く方法のすべての意味を理解することを期待することはできません。その
ため、優れたプラクティスの推奨事項があるため、必ずしも理由を理解しなくてもそれらに従うことができます。
(そして、それがまだ明らかでない場合、シェルでセキュリティに敏感なコードを書くことを避けてください)。
そして、このサイトの回答で変数を引用してください!
¹ ksh93
およびpdksh
派生物では、グロビングが無効になっていない限り、ブレース展開も実行されます(ksh93
ksh93u +までのバージョンの場合、braceexpand
オプションが無効になっている場合でも)。