
root権限を昇格させるための信頼性の高いエクスプロイト + 解説記事。(Ubuntu 22.04でテスト済み)
CVE-2022-37706

皆さん、今回は私が発見した最近の0-dayについてお話しします。Linuxの主要なウィンドウマネージャの一つであるEnlightenment (https://www.enlightenment.org/) に関するものです。
この0-dayは、任意のユーザーを非常に簡単かつ即座にroot権限に昇格させます。
このエクスプロイトはUbuntu 22.04でテストされていますが、どのディストリビューションでも問題なく動作するはずです。
まず、Enlightenmentはウィンドウマネージャ、コンポジタ、ミニマルデスクトップであり、Linux(主要プラットフォーム)、BSD、その他の互換性のあるUNIXシステム向けです。
このウィンドウマネージャを少し試してみるためにインストールしました。多くのツールが含まれていて、正直なところ見た目がかなりきれいなので興味深かったです。
apt install enlightenment を使ってパッケージをインストールした後、システム上のインストールされたファイルとディレクトリを調べました。多くのモジュールと多くのヘルパーバイナリがありましたが、最も興味深いのは次の点です:
➜ enlightenment cd /usr/lib/x86_64-linux-gnu/enlightenment/
➜ enlightenment find . -perm -4000
./utils/enlightenment_ckpasswd
./utils/enlightenment_system
./utils/enlightenment_sys
いくつかのSUIDバイナリをインストールします。それらを使ってrootに昇格できないか考えました。バイナリはすべて安全に見え、しっかりコーディングされていました。
これから説明するバイナリは enlightenment_sys です。
他のターゲットと同様に、事前評価を行った後に適用する戦略を選択します。まだの場合はこちらのブログを参照してください (https://pwn-maher.blogspot.com/2020/10/vulnerability-assessment.html)
トップダウンアプローチでコードを監査しました。
このウィンドウマネージャはオープンソースなので、すべてのバイナリとモジュールのソースコードが利用可能です。
そこで最初に apt source enlightenment を実行してすべてのソースコードを取得し、少し掘り下げることでターゲットのバイナリコードにたどり着きました。
しかし、バイナリをデバッグするためにGhidraにロードして解析し、ブレークポイントなどを設定するためのアドレスを取得しました。
最初の試行ではシンボルは見つかりませんでしたが、それは比較的小さなバイナリであることが判明したので必要ありませんでした。
驚いたことに、Ghidraの逆コンパイルされた疑似コードを直接見る方が、ソースを直接見るよりも快適でした(マクロを避け、特定のコードブロックをコンパイルするために使用されるOSに対するチェックも避けられます)。
それでは分析を始めましょう。
1- バイナリを操作する。
ターゲットに関する情報を得るためにファイルを実行してみましょう:

バイナリを実行しても出力は得られません:

--help 引数を指定すると次の出力が得られました:

すみません、rootを取得するために使います。
次に、strace を使って、execve や openat などの疑わしいシステムコールを使用していないか確認します:
strace ./enlightenment_sys 2>&1 | grep open

許可なく改ざんできない場所にある既知のライブラリを開くだけです。
strace ./enlightenment_sys 2>&1 | grep exec

2- バイナリをリバースエンジニアリングしてからエクスプロイトする。
新しいGhidraプロジェクトを作成し、この特定のバイナリをロードしました。
シンボルが見つからなかったので、エントリポイントを使用してmain関数を見つけることができます。
entry関数の最初の引数はmain自身です。
将来の参照のためにmainに名前を変更しました。
少し下にスクロールすると、すでにsystem()関数が使用されているのがわかります。
pwnerとして、この特定の関数を呼び出すチャレンジに何日も費やしていますx)
メモリ破損のバグやヒープの問題を探してバイナリをリバースしましたが、実際には奇妙なコマンドインジェクションでした。
バイナリはsystemを実行する前にすべてのセキュリティ対策を取っていますが、残念ながらそこに私たちの入力を注入することができます。

さて、ではバイナリを上からsystem関数までたどり、そこに入力を注入してみましょう。
まずバイナリは最初の引数が --help または -h かどうかをチェックし、前に見たメッセージを表示します。

次に、特権をrootに昇格させます。

次に、ほぼすべての環境変数をunsetします(セキュリティ対策)。意図しない別のバイナリを呼び出さないためです。

つまり、入力した最初の引数が "mount" の場合、この分岐に入り、与えられたフラグをチェックし、それらのフラグはスタック上に設定されます。
次に、mountの後の次のパラメータがUUID=かどうかをチェックします。ここには入りたくないので、"/dev/../tmp/;/tmp/exploit" を与えました。

これで410行目のチェック、strncmpチェックを通過します。
なぜなら、/dev/で始まらないとバイナリは終了するからです。
次に、提供したファイルに対してstat64の呼び出しがあります。";" という名前のフォルダを作成できることに注意してください。これによりコマンドインジェクションが発生します。
これまでのところ、エクスプロイトは既にこのファイル /dev/../tmp/;/tmp/exploit を作成していますが、これは呼び出されるエクスプロイトではありません。


system()に近づいてきました。
ここでp(ポインタ)がSUIDバイナリに与えられた最後の引数 /tmp///net に更新されます。
/tmp/net を渡せるのに、なぜ /tmp///net を提供するのか?
次のチェックをバイパスするためです:
if (((next_next == (char *)0x0) || (next_next[1] == '\0')) || ((long)next_next - (long)p != 6))
/tmp/net が存在し、/tmp/// の長さが6である必要がありました。
最後のstat64は "/dev/net" の存在をチェックします。
__snprintf_chk(cmd,0x1000,1,0x1000,"/dev%s",next_next);
そしてそれを見つけるので、最後のチェックを通過します。
ここでいくつかのファイルの利用可能性をチェックしますが、この時点では重要ではありません。すべて準備が整い、任意のコマンド実行をトリガーする寸前だからです。
ここで eina_strbuf_new() は system に渡されるコマンドを初期化します。問題は、私たちが次のように入力したことです:
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), "/dev/../tmp/;/tmp/exploit" /tmp///net
しかし、バイナリは eina_strbuf_append_printf() を数回呼び出し、次のようになります:
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), /dev/../tmp/;/tmp/exploit /tmp///net
ダブルクォーテーションが削除され、/tmp/exploit をrootとして呼び出すことができるようになります。

バイナリは意図しない動作を軽減するために最善を尽くしましたが、いつものように何でもpwnできます。このような論理バグを使ってエクスプロイトできるとは予想していませんでした。
次のCVEはメモリ破損によるLPE rootにしたいです。
Twitter disclosure: https://twitter.com/maherazz2/status/1569665311707734023