重大な脆弱性(CVE-2017-5932)で少し話題になったbash4.4の補完機能の便利な点

## bash4.4の脆弱性(CVE-2017-5932)

bash4.4をシェルとして使っている方はおりますでしょうか？ 

まだ使っている人があまりいないからなのか、私の感覚としては脆弱性の内容のインパクトの割にそんなに話題になってないような気がするのですが、CVE-2017-5932はなかなか怖い脆弱性です。

もし使っている方がいらっしゃいましたら、まずCVE-2017-5932の対策が済んでいるか早急にご確認下さい。

[bash_completion_vuln - GNU Bash code execution vulnerability in path completion](https://github.com/jheyens/bash_completion_vuln/blob/master/2017-01-17.bash_completion_report.pdf)

### どんな脆弱性なのか？

bashの補完機能によって、任意のコマンドが実行されてしまう脆弱性です。
下記のようにサーバの中にあやしいファイルを見つけて、うっかりrmコマンドに続いてTABを押そうものなからその時点でアウトです。攻撃手法としては見事ですが、恐ろしい脆弱性ですね。

```shell:
$ echo $SHELL # シェル確認
/bin/bash
$ /bin/bash --version # bashのバージョン確認
GNU bash, バージョン 4.4.6(7)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ ls
"`touch HereBeDragons`
$ rm \"\`touch\ HereBeDragons\` ^C # TAbを押してファイルを消そうとする
$ ls
"`touch HereBeDragons`  HereBeDragons # ファイル名の中のコマンドが実行されている!
$ 
```

#### 実画面

![2017:03:06 12-24-31.png](https://qiita-image-store.s3.amazonaws.com/0/90032/2a3c0d4b-8a4b-7ded-ceb6-08a341e8aff5.png)



### 対策

各ディストリビューションにパッケージ化されている場合は、お使いのディストリビューションのパッケージをアップデートすれば良いかと思いますが、単純にパッチを当てたい場合は、[Bash-4.4 patch 7](http://git.savannah.gnu.org/cgit/bash.git/commit/?id=4f747edc625815f449048579f6e65869914dd715)を適用すれば修正されます。

## bash4.4で良くなった補完機能

冒頭から重大な脆弱性があるというネガティブな話題からはじまりましたが、ソフトウェアに脆弱性があるのは当たり前なので継続的にアップデートをかけていこうという思いを改めて大切にしながら、4.4から良くなった面に目を向けていきましょう。

### bashの補完機能とは

シェルを使っている方には説明不要だと思いますが、念のため説明しておくと、補完機能とは下記のことを可能にするものです。

```bash:
$ echo $SHELL # 現在のユーザーのログインシェルを確認
/bin/bash
$ /bin/bash --version # bashのバージョン確認
GNU bash, version 4.1.2(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ mkdir ls_test # ls_testディレクトリを作成
$ cd ls_test # ls_testディレクトリに移動
$ touch a{a..d}{a..d} # bashのブレース展開機能を使って空ファイル作成
$ ls # ディレクトリ内のファイルを確認
aaa  aab  aac  aad  aba  abb  abc  abd  aca  acb  acc  acd  ada  adb  adc  add
$ ls # lsの後で空白１つ文字を入れてTABを押す
$ ls a # ディレクトリ内の全てのファイルの先頭文字がaなのでaまで自動的にカーソル上に表示される。
$ ls ab # そのままbを入力してTABを押す
aba  abb  abc  abd # abから始まるファイル名が候補として補完される
```

このようにコマンドラインの途中までの入力で、TABを押すと勝手にaが入力され、続いてbを押すと同じディレクトリ内でabに続くファイル名だけが絞り込まれました。これが補完機能です。
シェルのみならず、コマンドライン上で動作するプロセスにはお馴染みのユーティリティ機能ですね。

上記はlsコマンドにおいての補完対象なので、単純にディレクトリ内のファイルやディレクトリのみの補完でしたが、現在のユーザーが実行可能なコマンド名の補完など、入力中のコンテキストに応じて適切な補完候補が大抵の場合、特に設定せずに使うことができます。

大抵の場合というのは、ユーザーがbashを利用する時はほとんどの場合何らかのディストリビューションを利用しているはずなので、その依存関係の中でコマンドに応じた補完機能が使えるように設定してくれているからです。

インストールしたパッケージの中で、/ete/bash_completion.d/の中に、コマンドの補完候補を選ぶ処理が書かれたシェルスクリプトもついでにインストールしてくれてたりするのが、それに該当します。

このように、補完候補を選ぶ処理をシェルスクリプトで記述できるので、当然ユーザーが手軽に新規に作成したり編集することができます。たとえば、ユーザーのホームディレクトリの.bashrcに、下記の記述が書いてあるとします。

```shell:.bashrc
function _comp()  {
    COMPREPLY=(aaa bbbb ccc)
}
```

ログイン時に.bashrcが読み込まれている設定であれば、ログインと同時に_comp関数が使えるようになっています。

ログイン後に.bashrcを書き換えてからであればはbashのビルトインコマンドのsourceコマンド、あるいは同じくビルトインコマンドの.でファイルを読み込めば関数が使えるようになります。

```bash:
$ source ~/.bashrc
$ . ~/.bashrc
```

ちなみに、これから通常のコマンドとは別に、bashのビルトインコマンドという言葉が出てきます。

通常のコマンドは、bashから子プロセスとして生成されて実行される外部のコマンド、 たとえば/bin/の中に配置してある実行ファイルを指します。

対してbashのビルトインコマンドは、bashの実行ファイルの中に組み込まれているルーチンのことを指します。

echoなど、外部コマンドとビルトインコマンドで同名のものがあると、どちらが実行されているのか見かけ上わかりませんが、これはbashのビルトインコマンドtypeでどちらか判定することができます。

```bash:
$ type touch
touch is /bin/touch # touchは外部コマンド
$ type .
. is a shell builtin # .はシェルのビルトインコマンド
$ type source
source is a shell builtin # sourceはシェルのビルトインコマンド
$ type cd
cd is a shell builtin # cdはシェルのビルトインコマンド
$ type echo
echo is a shell builtin # echoはシェルのビルトインコマンド
$ which echo
/bin/echo # 同名のコマンドが存在する
$ type pwd
pwd is a shell builtin # pwdはシェルのビルトインコマンド
$ which pwd
/bin/pwd # 同名のコマンドが存在する
```

cdはビルトインコマンドだけに存在していますね。

こう考えると、bashのビルトインコマンドにしかないコマンドはsudoできず、sudo cdに相当する処理を実行したい場合は、そもそもbashごと起動しなければならないことがわかります。

続いてbashのビルトインコマンド、completeで補完機能対象のコマンド(この場合はhoge)と先程の関数名を指定します。

```bash:
$ complete -F _comp hoge
```

これでhogeコマンドに対して、補完機能を追加することができました。

```bash:
$ hoge # hogeの後にスペースを追加してTABを押す
aaa  bbb  ccc
```

_comp関数内で設定したCOMPREPLYという配列変数の内容が補完候補として無条件で指定されています。
入力した文字によって絞り込んだり、もう少し細かい制御もできるのですが、これが基本形です。

現在適用されている補完関数を表示するには、completeコマンドだけを打ちます。

```bash:
$  complete
complete -F _comp hoge
```

特定のコマンドの補完機能を解除するには下記のようにrのオプションとコマンド名を指定します。

```bash:
$  complete -r hoge
```

さて、ここで先程の関数内のCOMPREPLY関数を書き換えてみます。

```shell:.bashrc
function _comp()  {
    COMPREPLY=(ccc bbb aaa)
}
```

再読込して、TABによる補完機能を試します。

```shell:
$ source ~/.bashrc
$ hoge # hogeの後にスペースを追加してTABを押す
aaa  bbb  ccc
```

配列変数の順番通りなら、ccc bbb aaaと表示されて欲しいところですが、ソートされてaaa  bbb cccと表示されています。

今回の例では、順番が変わったところでどうってことないですが、TABによる補完対象がソートされては嫌なケースもあります。
そのケースを確認する為に、下記のコマンドを実行して事前準備をします。

```bash:
$ mkdir hoge # 適当なディレクトリを作成
$ cd hoge
$ for file in a{a..d}{a..d}; do touch ${file}; sleep 1; done # 一秒おきにaaaからaddまでの空ファイルを作成
# シェルの制御が返って来るまで16秒程度待つ
$ ls
aaa  aab  aac  aad  aba  abb  abc  abd  aca  acb  acc  acd  ada  adb  adc  add
```

hogeというディレクトリの中に、1秒おきに16個のファイルを作成しました。1秒おきというのがポイントです。

先程のCOMPREPLY変数では補完要素をベタ書き指定しましたが、コマンド置換によって指定することも可能です。たとえば下記のような感じです。

```shell:.bashrc
function _comp()  {
    COMPREPLY=($(ls ~/hoge))
}
```

再読込して、TAB補完機能を試します。

```shell:
$ source ~/.bashrc
$ hoge # hogeの後にスペースを追加してTABを押す
hoge a # aだけが表示されるのでもう一度TABを押す
aaa  aab  aac  aad  aba  abb  abc  abd  aca  acb  acc  acd  ada  adb  adc  add
```

ところでlsコマンドはオプションを指定して、表示順をソートすることができます。

```bash:
$ cd hoge
$ ls -t # 更新時間の降順にソート
add  adc  adb  ada  acd  acc  acb  aca  abd  abc  abb  aba  aad  aac  aab  aaa
```

addが最後に作成されたファイルなので最初に表示されました。

これを利用して、たとえば、特定のディレクトリのログファイルを引数とするコマンドを自作したとして、そのコマンドがファイルの更新時刻によってソートされたファイル名順に補完してくれると便利そうです。というか、そうだったら便利なケースが個人的にあったんです(笑)

なので、補完の指定は当然こうすることも可能です。

```shell:.bashrc
function _comp()  {
    COMPREPLY=($(ls -t ~/hoge))
}
```

再読込して、TABによる補完機能を試します。

```shell:
$ source ~/.bashrc
$ hoge # hogeの後にスペースを追加してTABを押す
hoge a # aだけが表示されるのでもう一度TABを押す
aaa  aab  aac  aad  aba  abb  abc  abd  aca  acb  acc  acd  ada  adb  adc  add
```

更新時刻順でソートされてくれません。最初の例でみたように、bash内で勝手にソートされてしまうからです。

### bash4.4からソートを防ぐことができるようになった

結論から言うと、bash4.4以前ではソートされてしまう処理をどうこうする手段はありません。ですが、bash4.4からはbash内のソート処理をスキップするオプションが追加されています。

```bash:bash4.4
$ bash --version # バージョン確認
GNU bash, バージョン 4.4.12(2)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ source ~/.bashrc
$ complete -F _comp hoge # 4.4以前と同じ
$ hoge a # TABを押す
aaa  aab  aac  aad  aba  abb  abc  abd  aca  acb  acc  acd  ada  adb  adc  add # 勝手にソートされてる
$ complete -o nosort -F _comp hoge # 4.4から使えるnosortオプション
$ hoge a# TABを押す
add  adc  adb  ada  acd  acc  acb  aca  abd  abc  abb  aba  aad  aac  aab  aaa # 勝手にソートされない
```

このようにbash4.4ではcompleteコマンドのoオプションにnosortが追加され、bash内でのソート処理を禁止する指定が可能になりました。

次回は、そもそも何故補完候補がソートされてしまっていたのか？そして4.4ではどのようにソートしないような処理になっているのか？をソースコードを追いながら調べたので、__原理原則で理解するbashの補完機能__というマニアックなエントリーを書きます。