しかし、もし…?

しかし、もし…?

unix.stackexchange.comをしばらくフォローしている場合は、echo $varBourne / POSIXシェル(zshは例外)などのリストコンテキストに引用符なしの変数を残すことが非常に特別な意味を持つことに注意する必要があります。妥当な理由があったり、そうしてはいけません。

これについては、ここの多くのQ&Aで詳しく説明します(例:スペースやその他の特殊文字が原因でシェルスクリプトが停止するのはなぜですか?いつ二重引用符が必要ですか?シェル変数の拡張とglobと分割の影響引用符付きの文字列と引用符なしの文字列の拡張)

これは70年代後半のBourneシェルが最初にリリースされて以来、Kornシェル(その1つ)は変更されていません。David Kornの最大の後悔(問題#7))またはbash主にPOSIX / Unixによって指定されたKornシェルを複製します。

今、私たちはまだ多くの答えを見ることができ、時には変数を引用しない公に公開されているシェルコードも見ることができます。今頃なら、人々がそれを学んだと思います。

私の経験によれば、参照変数を省略する3つの主要なタイプの人がいます。

  • 初心者。これは完全に直感的ではない構文なので、すべて許すことができます。このサイトで私たちの役割はそれらを教育することです。

  • 物忘れがひどい人。

  • 何度も当たってもまだ確信がない人はこう思います。もちろん、Bourneシェルの作者は、私たちがすべての変数を参照したくありません。

そのような行動に関連するリスクをさらすと、おそらくそれらを説得することができます。

変数を引用するのを忘れた場合に起こりうる最悪の状況は何ですか?本物だそれ悪い?

ここではどのような脆弱性について話していますか?

どのような状況で問題が発生する可能性がありますか?

ベストアンサー1

ヘッダー

まず、これは問題を解決する正しい方法ではないことをお伝えしたいと思います。 「と言うのとちょっと似ています。人を殺してはいけない。そうでなければ刑務所に行きます。」。

同様に、変数を引用するとセキュリティホールが生じるため、変数を引用しません。そうしないのが間違っているので、変数を引用しています(しかし、刑務所への恐怖が役に立つとしたらどうしますか)。

今電車に乗りたい人のための簡単な要約です。

ほとんどのシェルでは、引用符のない変数拡張(およびこの答えの残りの部分はコマンド置換(`...`OR $(...))および算術拡張($((...))OR $[...])にも適用されますが)は非常に特別な意味を持ちます。これを説明する最善の方法は、一種の暗黙の呼び出しと同じです。分割+グローバルオペレータ 1.

cmd $var

他の言語では、次のように書かれています。

cmd(glob(split($var)))

$varまず、関連する特殊パラメータに従って$IFS分ける部分)、その分割による各単語が考慮されます。模様一致するファイルのリストに展開されます(全体的な状況部分)。

たとえば、$varContains*.txt,/var/*.xml$IFS Contains,cmd複数のパラメータで呼び出され、最初のパラメータは現在のディレクトリのファイルで、次のcmdパラメータはです。txtxml/var

cmd2つのリテラルパラメータとを使用してcmd 呼び出したい場合は、次のように*.txt,/var/*.xml書くことができます。

cmd "$var"

以下はもう少しおなじみの別の言語です。

cmd($var)

私たちが言ったことシェルの脆弱性

結局、セキュリティに敏感な状況ではシェルスクリプトを使用してはならないことが最初から知られていました。もちろんです。変数を引用符で囲まないままにしておくのは間違いです。しかし、それほど有害ではありません。そうですか?

まあ、一部の人はWeb CGIにシェルスクリプトを使用してはいけません。年9月)では、シェルが使用されてはならない場所(CGI、DHCPクライアントフックスクリプト、sudoersコマンド、呼び出しなど)で依然として広く使用されていることを明らかにしました。渡す(そうでなければ〜のように)setuidコマンド...

時には無意識に。たとえば、// CGIスクリプトsystem('cmd $PATH_INFO') では、シェルは実際にコマンドラインを解釈するために呼び出されます。 (シェルスクリプト自体がシェルスクリプトである可能性があり、作成者がCGIから呼び出されるとはまったく予期しなかった可能性があります。)phpperlpythoncmd

権限上昇のための道があるとき誰か(彼を呼ぼう)攻撃者)彼はしないでください。

常に意味攻撃者ほとんどの場合、バグが原因で実行しないことを誤って実行した特権ユーザー/プロセスによって処理されたデータを提供します。

既定では、障害のあるコードが次の制御下でデータを処理すると問題が発生します。攻撃者

今は必ずしも明確ではありません。データこれは、発生する可能性があり、コードが信頼できないデータを処理するかどうかを言うのが難しいことがよくあります。

変数に関する限り、CGIスクリプトの場合、データはCookie、パス、ホスト...およびその他のパラメータだけでなく、CGI GET / POSTパラメータであることは明らかです。

setuidスクリプト(他のユーザーが呼び出すときに1人のユーザーとして実行)の場合、これはパラメーターまたは環境変数です。

もう一つの非常に一般的なベクトルはファイル名です。ディレクトリからファイルのリストをインポートすると、ファイルがそのディレクトリに植えられた可能性があります。攻撃者

これに関連して、対話型シェルプロンプト(ファイル操作中など)/tmpでも脆弱になる可能性があります~/tmp

a も~/.bashrc脆弱である可能性があります (たとえば、一部の変数がクライアント制御下にあるサーバーのデプロイなどのタスクを実行するために呼び出す場合はbash説明可能)。sshForcedCommandgit

これで、信頼できないデータを処理するためにスクリプトを直接呼び出すことはできませんが、他のコマンドで呼び出すことができます。あるいは、間違ったコードをコピーしてスクリプトに貼り付けることもできます(そして3年後にあなたや同僚がコピーして貼り付けることができます)。特に見落とされやすい部分の一つは批判的コードのコピーがどこにあるかわからないので、Q&Aサイトの回答にあります。

家に近づけばどれくらいいいの?

引用符のない変数(またはコマンドの置き換え)は、シェルコードに関連するセキュリティの脆弱性の最大の原因です。これは、部分的には、これらのエラーがしばしば脆弱性として解釈されるだけでなく、引用されていない変数を見るのが一般的であるためです。

実際に、シェルコードの脆弱性を見つけるときに最初にすべきことは、引用符のない変数を見つけることです。これは見つけやすく、一般的に良い候補であり、通常、攻撃者が制御しているデータを簡単に追跡できます。

引用しない変数は、さまざまな方法で脆弱性になる可能性があります。ここでは、いくつかの一般的な傾向をお知らせします。

情報開示

ほとんどの人は、引用されていない変数に関連するエラーを経験します。分ける(たとえば、ファイル名にスペースが含まれるのが一般的で、スペースはIFSのデフォルトです。)多くの人はそれを無視します。 全体的な状況部分。これ全体的な状況部分的には少なくとも関連があります。 分ける部分。

整理されていない外部入力方式のワイルドカードの完成攻撃者すべてのディレクトリの内容を読むことができます。

存在する:

echo You entered: $unsanitised_external_input

$unsanitised_external_input含まれている場合は、/*次のことを意味します。攻撃者何を見ることができます/。あまりありません。ただし、個別に名前を付けることなく、他の危険な行動を提案し、サービスを有効にするためのシステム /home/*のユーザー名のリストを提供するので、より興味深いものになります。値はファイルシステム全体のみを一覧表示します。/tmp/*/home/*/.forward/etc/rc*/*/* /*/* /*/*/*...

サービス拒否の脆弱性。

前の例はあまりにも遠くに進み、DoSを受けました。

実際には、リストコンテキストで引用されていない変数とクリーンアップされていない入力は、少なくともDoSの脆弱性。

プロのシェルスクリプトでさえ、しばしば以下を引用することを忘れてしまいます。

#! /bin/sh -
: ${QUERYSTRING=$1}

:動作しないコマンドです。どのような問題が発生する可能性がありますか?

これは、設定されていない場合に割り当てられることを意味します$1。これは、コマンドラインからCGIスクリプトを呼び出すことを可能にする簡単な方法でもあります。$QUERYSTRING$QUERYSTRING

しかし、$QUERYSTRINGこれは参照されていないため、まだ拡張であり、分割+グローバル交換員に電話してください。

今、いくつかのグローブは、拡張するために特に高価です。これは、/*/*/*/*最大4つのレベルのディレクトリを一覧表示することを意味するので、それは非常に良いことではありません。これは、ディスクおよびCPUアクティビティに加えて、数万のファイルパス(ここでは10,000個のディレクトリを持つ最小のサーバーVMの場合は40,000個)を格納することを意味します。

これ/*/*/*/*/../../../../*/*/*/*は40k x 10kを意味し、 /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*最も強力な機械はひざまずくのに十分です。

自分で試してみてください。ただし、コンピュータがクラッシュしたり停止したりする可能性があることに備えてください。

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

もちろん、コードが次のような場合:

echo $QUERYSTRING > /some/file

その後、ディスクを埋めることができます。

ただGoogle検索をしてくださいシェルまたはバッシュCGIまたはクシュケジ、シェルでCGIを作成する方法を示すページを見つけることができます。処理パラメータの半分が脆弱であることに注意してください。

でもデビッド・コーヘンの一つ 脆弱です(クッキーの処理を参照)。

最大ランダムコード実行の脆弱性

ランダムなコード実行は、最も深刻な種類の脆弱性です。攻撃者どのコマンドでも実行でき、実行できる操作に制限はありません。

これは一般的にそうです分けるこれにつながる部分です。このように分割すると、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、、、(GNU one)、... )を実行できるコマンド、特にGNUバリアント(引数の後にオプションを許可する)で問題になります。時には、コマンドが他のコマンド(例えば、'sまたは...)を実行できると疑わないことがあります。envsedperlfindkshbashzsh[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実装(ほとんどの商用Unicesと一部のBSDを含む)を含むファイルを生成する場合、そのシェルがコマンドの数値比較演算子の算術評価を実行するため、そのファイルがsh 実行されます。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 ]

これは私が迷惑に思うものです。引用符を省略できるかどうかを判断するために、特定の拡張子に問題があるかどうか疑問に思う人もいます。

まるで言うように。こんにちは、分割+グローブ演算子で囲むことができないようです$#。シェルで分割+グローブを行うようにします。。またはいや、バグが当たる確率が少ないから、間違ったコードを書こう

今はその可能性はどのくらいですか?いいですね。$#(または$!$?または算術置換)数字(または-一部²)のみを含めることができるため全体的な状況部品はすでに出てきました。 ~のため分ける$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設定の副作用があり、次の[ コマンドは失敗します。パラメータが多すぎます。バイパス。

いつ分割+グローバル交換員が呼び出されませんか?

変数やその他の拡張に引用符が必要な別のケースがあります。パターンとして使用する時です。

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

$aとが等しいかどうかテストしませんが、$b(除くzsh$aifとのパターンと一致します$b$b文字列で比較するには引用符を付ける必要があります(パターンと見なされない場合は、"${a#$b}"or"${a%$b}"またはwhereで"${a##*$b*}"同じ引用符を付ける必要があります)。$b

つまり、両方が異なる[[ $a = $b ]]場合(たとえば、isとisの場合)、trueを返し、同じ場合(たとえば、両方とareの場合)、falseを返します。$a$b$aanything$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]'

今パターンが一致し、分割+グローバル演算子の適用、引用されていない変数のリスクは何ですか?

私は次のように書いたことを認めなければなりません:

a=$b
case $a in...

ここに参照を含めるのは悪いことではありませんが、必ずしも必要ではありません。

ただし、このような状況(Q&Aの回答など)で引用符を省略すると、初心者に誤ったメッセージを送信できるという副作用があります。変数を引用しなくても大丈夫でしょう。

a=$b例えば、彼らはそれが可能であればそうすることができると考え始めることができますexport a=$b(これはそれは多くの殻にありませんexportリストコンテキストで)またはenv a=$b

しかし、提案を受け入れない場所もあります。主なものは多くのシェルでKornスタイルの算術式です。たとえば、引用が許可されていないecho "$(( $1 + 1 ))" "${array[$1 + 1]}" "${var:$1 + 1}"場合$1(リストのコンテキスト - 単純なコマンドの引数 - しかし、完全な拡張には依然として引用が必要です)。

内部的には、シェルはCに触発された完全に独立した言語を理解しています。kshたとえば、AT&TではCと同じように3に拡張されますが、C$(( 'd' - 'a' ))とは異なります。$(( d - a ))ksh93では二重引用符は無視されますが、他の多くのシェルでは構文エラーが発生します。 C では、"d" - "a"C 文字列へのポインタ間の差が返されます。シェルで同じことを行うことは意味がありません。

何についてzsh

zsh実際にはほとんどのデザイン当惑感を解決します。zsh(少なくともsh / kshエミュレーションモードではない場合)必要に応じて分けるまたはワイルドカードまたはパターンマッチング、変数の内容をパターンに$=var分割、グローバル、または処理するなど、明示的に要求する必要があります。$~var

ただし、引用符のないコマンドの置き換えでは、分割(ワイルドカードを除く)はまだ暗黙的に実行されます(図を参照echo $(cmd))。

また、変数を引用しないと、不要な副作用が発生することがあります。削除をクリア。この動作は、ワイルドカード(使用)と分割(使用)zshを完全に無効にして他のシェルで達成したものと似ています。まだの中:set -fIFS=''

cmd $var

ないでしょう。分割+グローバルただし、$var空の場合はcmd空の引数を受け取る代わりに引数を受け取りません。

これはエラーを引き起こす可能性があります(明らかな事実です[ -n $var ])。これにより、スクリプトの期待と仮定が壊れ、脆弱性が発生する可能性があります。

空の変数がパラメータを生成する可能性があるため削除済み、これは、次の引数が無効なコンテキストで解釈される可能性があることを意味します。

例えば、

printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2

$attacker_supplied1空の場合、文字列(for)ではなく$attacker_supplied2算術式(for)として解釈されます。%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 すべてのコマンド実行されます。

また、代わりにワイルドカードはデフォルトでは行われませんが、zshのワイルドカードは他のシェルのワイルドカードよりもはるかに強力であるため、オプションも有効または無効にせずに誤っていくつかのzsh変数を残すと、より多くのダメージを与える可能性があります。します。引用されていません。globsubstextendedglobbareglobqual

たとえば、さらに:

set -o globsubst
echo $attacker_controlled

eたとえば、glob修飾子の評価を使用してコマンドがglob拡張の一部として実行される可能性があるため、任意のコマンド実行の脆弱性になります。

$ set -o globsubst
$ attacker_controlled='.(e[uname])'
$ echo $attacker_controlled
Linux
.
emulate sh # or ksh
echo $attacker_controlled

bareglobqualsh / kshエミュレーションでは無効になっているため、ACEの脆弱性を引き起こしません(shなどのDoSの脆弱性はまだ存在しますが)。globsubstsh/ksh コードを解釈する場合は、sh/ksh エミュレーションの外部でこれらの機能を有効にする理由はありません。

だからあなたはする必要分割+グローバルオペレーター?

はい、これは通常、変数の引用を解除したい場合です。しかし、その後は必ず調整をしなければなりません。分けるそして全体的な状況ご使用の前に正しくお使いください。欲しいなら分ける代わりに部分全体的な状況セクション(ほとんどの場合)でワイルドカード(set -o noglob/ set -f)を無効にして変更する必要があります$IFS。そうしないと、上記のDavid KornのCGIの例などの脆弱性が発生する可能性があります。

結論として

簡単に言えば、シェルで引用されていない変数(またはコマンド置換または算術拡張)は実際には非常に危険である可能性があります。

これが考慮される理由の一つである。悪い習慣

これまで読んでくれてありがとう。想像以上なら心配しないでください。誰もがコードを書くようにコードを書くことのすべての意味を理解することは期待できません。だから私たちは ベストプラクティスの推奨事項、理由を理解することなく従うことができます。

(まだ明確でない場合は、シェルにセキュリティに敏感なコードを書かないでください。)

そしてこのサイトの回答に変数を引用してください!


¹ksh93合計pdkshと導関数で、支柱の拡張ワイルドカードが無効になっていない場合は実行されます(ksh93ksh93u +バージョンでこのbraceexpandオプションが無効になっていても)。

²ksh93および では、算術拡張に、、などもyash含めることができます。 glob演算子を含む多くの機能がありますが、シミュレーションでも算術拡張では分割+globは行われません。1,21e+66infnanzsh#extendedglobzshsh

おすすめ記事