S-JIS[2014-09-26] 変更履歴

bash ShellShock

bashのShellShock脆弱性(CVE-2014-6271)についてのメモ。


概要

/bin/bashのShellShock(と名付けられた)脆弱性は、2014年9月24日に報告された(日本では25日に騒ぎになった)脆弱性。

環境変数に(特定の書式に従って)コマンドを仕込むと、bash実行時にそのコマンドが実行されてしまうというもの。

例えばCGIの場合、HTTPリクエストの内容をそのまま環境変数に入れてシェルを実行する構造になっていると、攻撃者がHTTPリクエストにコマンドを仕込むことでそのコマンドが実行されてしまうので、非常に危険。


脆弱性有無の確認方法

bashのバージョンが新しければ大丈夫だが、OSのディストリビューションによって大丈夫なバージョンが異なる。
(自分の周りで使っているOSはほとんど大丈夫で、MacとCygwinのbashは駄目だった)

$ bash -version
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
〜

なので、脆弱性があるかどうかを確認するのは、実際にコマンドを実行してみるのが一番確実。

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
確認用コマンドの実行結果
脆弱性がある場合 問題ない場合
vulnerable
this is a test
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

この脆弱性がある場合は、「vulnerable」と表示される。(vulnerableは「脆弱」という意味らしい)
対処されているバージョンでは、関数を定義しようとしたというエラーになる。


脆弱性確認用コマンドの意味

上述の脆弱性確認用のコマンドがどういう意味なのか解析してみる。

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

まず、これはenvコマンドを実行している。
envコマンドは、環境変数を設定し、その環境を使ってコマンドを実行するもの。「env 環境変数定義 実行コマンド」という形式。
例えば以下のような感じで使う。

$ env aaa=123  bash -c 'echo $aaa'
123

なので、脆弱性確認用コマンドでは、環境変数xに値「() { :;}; echo vulnerable」を設定し、コマンド「bash -c "echo this is a test"」を実行する。
「bash -c」は、引数の文字列をbashのコマンドとして実行する。つまり「echo this is a test」が実行される。

結果として、脆弱性が無ければ「this is a test」だけが表示されるはずである。


環境変数に入れている「() { :;}; echo vulnerable」はどういう意味か。

bashでは関数を定義することが出来る。

$ myFunc() {
>   echo my-func
> }

$ myFunc() { echo my-func; }	←1行で書くことも出来る。その場合は、改行の代わりにセミコロンが必要

$ myFunc
my-func

そして、関数定義をエクスポートすることが出来る。(普通はexportでは環境変数をエクスポートするが、-fを付けると関数をエクスポートできる)

$ export -f myFunc

この場合、「関数名を環境変数名、関数定義内容を値」とする環境変数として定義される。
引数なしのenvコマンドを使うと定義されている環境変数一覧が出てくるので、そこで内容を見ることが出来る。

脆弱性がある版の場合 脆弱性対応版の場合
$ env | grep -A1 myFunc
myFunc=() {  echo my-func
}
$ env | grep -A1 myFunc
BASH_FUNC_myFunc()=() {  echo my-func
}

つまり、環境変数の値において、丸括弧から始まって波括弧で囲んだものは関数の内容と同等になる。

したがって、今回の脆弱性を対応していないバージョンでは、(関数としてでなく)環境変数として関数内容を定義した場合でも、(新たに起動したbash上であれば)関数として使える。

脆弱性がある版の場合 脆弱性対応版の場合
$ export myEnv='() { echo my-env; }'

$ myEnv
-bash: myEnv: コマンドが見つかりません

$ bash -c myEnv
my-env

$ echo 'myEnv' > test.sh
$ chmod +x test.sh
$ ./test.sh
my-env
$ export myEnv='() { echo my-env; }'

$ myEnv
bash: myEnv: コマンドが見つかりません

$ bash -c myEnv
bash: myEnv: コマンドが見つかりません

$ echo 'myEnv' > test.sh
$ chmod +x test.sh
$ ./test.sh
./test.sh: line 1: myEnv: コマンドが見つかりません
$ export BASH_FUNC_myFunc()='() { echo my-env; }'
bash: 期待してない token `(' のあたりにシンタックスエラー

$ export 'BASH_FUNC_myFunc()'='() { echo my-env; }'
bash: export: `BASH_FUNC_myFunc()=() { echo my-env; }': not a valid identifier

$ export BASH_FUNC_myFunc\(\)='() { echo my-env; }'
bash: export: `BASH_FUNC_myFunc()=() { echo my-env; }': not a valid identifier
  脆弱性対応版では、関数をエクスポートすると環境変数名としては「BASH_FUNC_」と丸括弧が付くようになったようで、
関数名そのもので定義した環境変数では実行することは出来なくなった。
また、その名称は環境変数として定義することも出来ない。

なので、脆弱性確認用コマンドで環境変数に設定している「() { :;}」は、関数定義の内容だということだ。
x='() { :;}'」という環境変数定義は、関数xを定義したのと同じ意味になる。

コロン「:」は何かと言うと、bashのビルトイン(組み込み)コマンドの「何もしない」命令
bashの関数定義では中身が空っぽの関数(「func() { }」)は作れないので、何らかのコマンドを書く必要がある。ので、字数を一番短く済ませようとしたら「:」になるのだろう。
ここは関数定義なので、実際に存在しないコマンドでも何でも良い。だからピリオド「.」とかでも大丈夫だけど。

ちなみに丸括弧「()」と波括弧開始「{」の間や、波括弧開始「{」の後には空白(または改行)が必要。
(環境変数に入れる際には)丸括弧「()」と波括弧開始「{」の間に空白が無いと関数定義として認識されない。
波括弧開始「{」の後に空白が無いと関数定義としてはエラーになる。
波括弧終了「}」の前(というか各コマンドの末尾)にも改行が必要で、1行で書きたい場合は改行の代わりにセミコロン「;を入れる。


今回の脆弱性に関してもうひとつ重要なことは、bashコマンドは、実行に先立って、内部で環境変数一覧を作成するということ。

今回の脆弱性は、その環境変数一覧作成時に、環境変数の値に入っている関数定義の後の文字列がコマンドとして実行されてしまうというもの。
一覧を作成する際の話なので、全ての環境変数が対象になる。つまり今回の脆弱性では、環境変数名を問わない。どんな環境変数名でも対象になってしまう。

脆弱性確認用コマンドの環境変数に入れた値「() { :;}; echo vulnerable」では、関数定義「() { :;}」の後に「echo vulnerable」があるので、bash実行時にそれが実行されてしまう。

bashの実行というのは、脆弱性確認用コマンドでは明示的に「bash -c」でbashを起動したが、シェルスクリプトの実行でもシェルが起動されるし、sshでも起動されるらしい。
デフォルトのシェル(やシェルスクリプト内で指定されたシェル)がbashであれば、bashが起動される。
他言語で作ったプログラムからシェルを実行する場合も同様。


以上の話をまとめると、脆弱性確認用のコマンドは(その方が便利なので)1行で実行できるようになっているが、分割して試すことも出来る。

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

$ bash -c "echo this is a test"

※環境変数を定義した時点では何も表示されないが、その後にbashコマンドを実行した時点で、(脆弱性があると)環境変数内の関数定義の後のコマンドが実行される

環境変数名は何でもいいし、関数定義の中身は「:」でなくてもいい。一行で実行する場合は「env」も省略できる。
くりすさんの例はそうなっている。

$ AHO='() { baka; }; echo manuke' bash -c 'echo Hi'

「bash -c」だけでなく、任意のシェルスクリプトでも発動する。

$ env x='() { :;}; echo vulnerable' /tmp/test.sh

(test.shの実行というのは、要するに「bash test.sh」を実行しているのと同じだから。→シェバン


bash起動時に環境変数一覧を作成する際の話なので環境変数名は何でもいいし、bashが起動しさえすれば、実行されるコマンド自体も何でもいい。
これが今回の脆弱性の恐ろしいところ。


シェルスクリプトへ戻る / 技術メモへ戻る
メールの送信先:ひしだま