web-dev-qa-db-ja.com

env x = '(){:;};とはcommand 'bash doとなぜそれが安全でないのですか?

明らかにbashに脆弱性(CVE-2014-6271)があります: Bash特別に細工された環境変数コードインジェクション攻撃

私は何が起こっているのかを理解しようとしていますが、私はそれを理解していることを完全に確信していません。単一引用符で囲まれているechoを実行するにはどうすればよいですか?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

EDIT 1:パッチを適用したシステムは次のようになります:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

EDIT 2:関連する脆弱性/パッチがあります: CVE-2014-7169 わずかに異なるテストを使用します:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

パッチされていない出力

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

部分的(初期バージョン)パッチ出力

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

パッチ出力CVE-2014-7169まで:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

EDIT 3:ストーリーは続きます:

244
jippie

bashは、エクスポートされた関数定義を環境変数として保存します。エクスポートされた関数は次のようになります。

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

つまり、環境変数fooにはリテラルの内容があります。

() {  bar
}

Bashの新しいインスタンスが起動すると、これらの特別に細工された環境変数を探し、それらを関数定義として解釈します。あなたも自分で書いて、それがまだ機能することを確認することができます:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

残念ながら、文字列(環境変数)からの関数定義の解析は、意図したよりも広い影響を与える可能性があります。パッチが適用されていないバージョンでは、関数定義の終了後に発生する任意のコマンドも解釈します。これは、環境で受け入れ可能な関数のような文字列を決定する際の制約が不十分なためです。例えば:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

関数定義の外側のエコーは、bashの起動時に予期せず実行されていることに注意してください。関数定義は、評価と悪用を行うためのステップに過ぎず、関数定義自体と使用される環境変数は任意です。シェルは環境変数を調べ、fooを確認します。これは、関数定義がどのように見えるかについて知っている制約を満たしているように見え、行を評価し、意図せずにエコーも実行します(任意のコマンドである可能性があります) 、悪意があるかどうか)。

通常、変数自体には、変数に含まれる任意のコードの呼び出しを直接引き起こすことは許可または期待されていないため、これは安全ではないと見なされています。おそらく、プログラムが信頼できないユーザー入力から環境変数を設定しています。これらの環境変数が、ユーザーがコードで宣言された理由により、その環境変数を使用して明示的に意図せずに任意のコマンドを実行できるような方法で操作される可能性があることは、非常に予想外です。

これは実行可能な攻撃の例です。存続期間の一部として、脆弱なシェルをどこかで実行するWebサーバーを実行します。このWebサーバーは環境変数をbashスクリプトに渡します。たとえば、CGIを使用している場合、HTTPリクエストに関する情報は多くの場合、Webサーバーからの環境変数として含まれます。例えば、 HTTP_USER_AGENTは、ユーザーエージェントのコンテンツに設定される場合があります。これは、ユーザーエージェントが '(){:; }; echo foo '、そのシェルスクリプトが実行されると、echo fooが実行されます。繰り返しますが、echo fooは悪意のあるものかどうかにかかわらず、何でもかまいません。

209
Chris Down

これは、何が起こっているかをさらに示すのに役立ちます。

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

脆弱なシェルを実行している場合、新しいサブシェルを開始すると(ここでは、単にbashステートメントを使用して)、任意のコード(echo "pwned")は、その開始の一部としてすぐに実行されます。どうやら、シェルは環境変数(ダミー)に関数定義が含まれていることを確認し、その定義を評価してその環境でその関数を定義します(関数を実行していないことに注意してください。「hi」と表示されます)。

残念ながら、これは関数定義を評価するだけでなく、環境変数の値のテキスト全体を評価します。これには、関数定義に続く悪意のある可能性のあるステートメントも含まれます。初期の関数定義がないと、環境変数は評価されず、単にテキスト文字列として環境に追加されることに注意してください。 Chris Downが指摘したように、これはエクスポートされたシェル関数のインポートを実装するための特定のメカニズムです。

新しいシェルで定義された関数が表示され(そこにエクスポート済みとしてマークされている)、実行できます。さらに、ダミーはテキスト変数としてインポートされていません:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

この関数の作成も、それが実行されたとしても何もしないことは、エクスプロイトの一部ではありません。これは、エクスプロイトが実行される手段にすぎません。重要なのは、攻撃者が、エクスポートされた環境変数に入れられるテキスト文字列で、最小限の重要でない関数定義が前に付いた悪意のあるコードを提供できる場合、サブシェルが開始されるときに実行され、これは一般的なイベントです多くのスクリプトで。さらに、スクリプトの権限で実行されます。

86
sdenham

これは、上記のChris Downによる優れた回答のチュートリアルスタイルのリキャストとして書いたものです。


Bashでは、このようなシェル変数を持つことができます

_$ t="hi there"
$ echo $t
hi there
$
_

デフォルトでは、これらの変数は子プロセスに継承されません。

_$ bash
$ echo $t

$ exit
_

ただし、それらをエクスポート用にマークすると、bashはサブプロセスの環境に移動することを意味するフラグを設定します(envpパラメーターはあまり表示されませんが、Cプログラムのmainは3つのパラメーター:main(int argc, char *argv[], char *envp[])(ポインターの最後の配列は、シェル変数の配列とその定義)。

したがって、次のようにtをエクスポートしましょう。

_$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit
_

上記のtはサブシェルで定義されていませんでしたが、エクスポートした後に表示されるようになりました(エクスポートを停止する場合は_export -n t_を使用してください)。

しかし、bashの機能は別の動物です。次のように宣言します。

_$ fn() { echo "test"; }
_

そして今、あなたはそれを別のシェルコマンドであるかのように呼び出すことで、関数を呼び出すことができます:

_$ fn
test
$
_

ここでも、サブシェルを生成した場合、関数はエクスポートされません。

_$ bash
$ fn
fn: command not found
$ exit
_

_export -f_で関数をエクスポートできます:

_$ export -f fn
$ bash
$ fn
test
$ exit
_

ここでトリッキーな部分があります。fnのようなエクスポートされた関数は、シェル変数tのエクスポートが上記のように環境変数に変換されます。これは、fnがローカル変数の場合は発生しませんが、エクスポート後はシェル変数として表示されます。ただし、またはを使用して、同じ名前の通常の(つまり、関数ではない)シェル変数を使用できます。 bashは変数の内容に基づいて区別します。

_$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 
_

これで、envを使用して、エクスポート用にマークされたすべてのシェル変数を表示でき、通常のfnと関数fnの両方が表示されます。

_$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$
_

サブシェルは両方の定義を取り込みます:1つは通常の変数として、もう1つは関数として:

_$ bash
$ echo $fn
regular
$ fn
test
$ exit
_

上記のようにfnを定義するか、通常の変数割り当てとして直接定義できます。

_$ fn='() { echo "direct" ; }'
_

これは非常に珍しいことです。通常、上記のfn() {...}構文で行ったように、関数fnを定義します。しかし、bashはそれを環境を通じてエクスポートするため、上記の通常の定義に直接「ショートカット」できます。 (あなたの直感に反して、おそらく)これはnotを実行することに注意してください。現在のシェルで使用可能な新しい関数fnが生成されます。ただし、** sub ** Shellをスポーンした場合は、そうなります。

関数fnのエクスポートをキャンセルして、新しい通常のfn(上記のとおり)をそのままにしておきましょう。

_$ export -nf fn
_

現在、関数fnはエクスポートされていませんが、通常の変数fnはエクスポートされており、_() { echo "direct" ; }_が含まれています。

これで、サブシェルが_()_で始まる通常の変数を検出すると、残りを関数定義として解釈します。しかし、これはonlyであり、新しいシェルが開始されます。上記で見たように、_()_で始まる通常のシェル変数を定義するだけでは、関数のように動作しません。サブシェルを開始する必要があります。

そして「Shellshock」のバグ:

先ほど見たように、新しいシェルが_()_で始まる通常の変数の定義を取り込むと、それは関数として解釈されます。ただし、関数を定義する中括弧の後にさらに指定がある場合は、何でも実行も存在します。

これらはもう一度要件です:

  1. 新しいbashが生成されます
  2. 環境変数が取り込まれる
  3. この環境変数は「()」で始まり、中括弧内に関数本体が含まれ、その後にコマンドがあります

この場合、脆弱なbashが後者のコマンドを実行します。

例:

_$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$
_

通常のエクスポートされた変数exは、関数exとして解釈されたサブシェルに渡されましたが、後続のコマンドはサブシェルが生成されたときに実行されました(_this is bad_)。


滑らかな1行テストの説明

Shellshockの脆弱性をテストするための人気のある1行は、@ jippieの質問で引用されたものです。

_env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
_

内訳は次のとおりです。最初にbashの_:_はtrueの省略形です。 trueと_:_は両方とも、bashで(ご想像どおり)trueに評価されます。

_$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$
_

次に、envコマンド(これもbashに組み込まれています)は環境変数を出力します(上で見たように)が、そのコマンドに与えられたエクスポートされた変数(または変数)を使用して単一のコマンドを実行するためにも使用できます。 _bash -c_は、コマンドラインから単一のコマンドを実行します。

_$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$
_

したがって、これらすべてのものを縫い合わせて、bashをコマンドとして実行し、それにダミーの処理(_bash -c echo this is a test_など)を与え、_()_で始まる変数をエクスポートして、サブシェルが次のように解釈できるようにします機能。 Shellshockが存在する場合は、サブシェル内の後続のコマンドもすぐに実行されます。渡す関数は私たちには無関係です(しかし解析する必要があります!)考えられる最も短い有効な関数を使用します。

_$ f() { :;}
$ f
$ 
_

ここでの関数fは、_:_コマンドを実行するだけで、trueを返して終了します。次に、その「悪」なコマンドに追加して、通常の変数をサブシェルにエクスポートします。ここにもう一度ワンライナーがあります:

_$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
_

したがって、xは、_echo vulnerable_が最後に付加された単純な有効な関数を持つ通常の変数としてエクスポートされます。これはbashに渡され、bashはxを関数(これは気にしません)として解釈し、Shellshockが存在する場合は_echo vulnerable_を実行する可能性があります。

_this is a test_メッセージを削除して、ワンライナーを少し短くすることができます。

_$ env x='() { :;}; echo vulnerable' bash -c :
_

これは_this is a test_を気にしませんが、サイレント_:_コマンドをもう一度実行します。 (_-c :_を省略した場合は、サブシェルに座って手動で終了する必要があります。)おそらく、最もユーザーフレンドリーなバージョンは次のようになります。

_$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the Word vulnerable above, you are vulnerable to Shellshock"
_
72
Fixee

プログラムに任意の環境変数を与えることができる場合は、選択したライブラリをロードすることで、ほとんどすべてのことを実行させることができます。ほとんどの場合、これはそれらの環境変数を受け取るプログラムの脆弱性ではなく、外部の人が任意の環境変数をフィードできるメカニズムの脆弱性と見なされます。

ただし、CVE-2014-6271は異なります。

環境変数に信頼できないデータが含まれていても問題はありません。プログラムの動作を変更する可能性のある環境変数に配置されないようにする必要があります。もう少し抽象的に言えば、特定の呼び出しについて、外部の人が直接指定できる環境変数名のホワイトリストを作成できます。

CVE-2014-6271のコンテキストで提唱されている例は、ログファイルの解析に使用されるスクリプトです。それらには、環境変数で信頼できないデータを渡す非常に正当なニーズがあるかもしれません。もちろん、そのような環境変数の名前は、悪影響を及ぼさないように選択されます。

しかし、これがこの特定のbashの脆弱性の悪い点です。任意の変数名を介して悪用される可能性があります。 GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPTという環境変数を作成すると、独自のスクリプト以外のプログラムがその環境変数の内容を解釈することは期待できなくなります。しかし、このbashバグを利用することで、すべての環境変数が攻撃ベクトルになります。

これは、環境変数の名前が秘密であることが期待されることを意味しないことに注意してください。関連する環境変数の名前を知っていても、攻撃は容易ではありません。

program1program2を呼び出し、次にprogram3を呼び出す場合、program1は環境変数を介してprogram3にデータを渡すことができます。各プログラムには、設定する環境変数の特定のリストと、それが作用する特定のリストがあります。 program2で認識されない名前を選択した場合、program1に悪影響を与えることを心配せずに、program3からprogram2にデータを渡すことができます。

program1によってエクスポートされた変数の正確な名前とprogram2によって解釈された変数の名前を知っている攻撃者は、この知識を悪用して、名前のセットに重複がない場合、「program2」の動作を変更することはできません。

しかし、program2bashスクリプトである場合、これは故障しました。このバグにより、bashはすべての環境変数をコードとして解釈するためです。

20
kasperd

あなたがリンクした記事で説明されています...

bashシェルを呼び出す前に、特別に細工された値で環境変数を作成できます。これらの変数には、シェルが呼び出されるとすぐに実行されるコードを含めることができます。

つまり、-c "echo this is a test"で呼び出されたbashは、呼び出されたときにコードを一重引用符で囲んで実行します。

Bashには関数がありますが、実装は多少制限されており、これらのbash関数を環境変数に入れることができます。この欠陥は、(環境変数内の)これらの関数定義の最後に追加のコードが追加されたときにトリガーされます。

投稿したコード例は、割り当てられたbashが割り当ての実行後にこの文字列の評価を停止しないという事実を悪用していることを意味します。この場合の関数割り当て。

私が理解しているように、投稿したコードスニペットの実際の特別な点は、実行するコードの前に関数定義を置くことにより、いくつかのセキュリティメカニズムを回避できることです。

9
Bananguin