
PoC
CVE-2022-37706

こんにちは、今回は私がLinuxの主要なウィンドウマネージャーの1つであるEnlightenment (https://www.enlightenment.org/) で見つけた最近の0-dayについてお話しします。
この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にロードし、ブレークポイントなどを設定するためのアドレスを取得しました。
最初の試行ではシンボルは見つかりませんでしたが、比較的小さなバイナリであることが判明したため、シンボルは必要ありませんでした。
驚くべきことに、srcを直接見るよりも、Ghidraの逆コンパイルされた疑似コードを見る方が非常に快適でした(マクロを避け、特定のコードブロックをコンパイルするために使用されるOSに対するチェックも避けられます)。
それでは解析を始めましょう。
1- バイナリをいじってみる。
ファイルを実行して、ターゲットに関する情報を見てみましょう:
Screenshot
バイナリを実行しても出力は得られません:
Screenshot
--help 引数を渡すと次の出力が得られました:
Screenshot
すみません、rootを取得するためにこれを使います。
次に、straceを使って、execveやopenatのような怪しいシステムコールを使用するかどうか見てみましょう:
strace ./enlightenment_sys 2>&1 | grep open
Screenshot
既知のライブラリを、私たちが改ざんする権限を持たない場所で開くだけです。
strace ./enlightenment_sys 2>&1 | grep exec
Screenshot
2- バイナリをリバースエンジニアリングして、エクスプロイトしましょう。
新しいGhidraプロジェクトを作成し、この特定のバイナリをロードしました。
シンボルが見つからなかったため、entryを使ってmain関数を見つけることができます。
entry関数の最初の引数はmain自身です。
将来の参照のために、それをmainにリネームしました。
少し下にスクロールすると、system() 関数が使用されているのがすでに見えます。
pwnerとして、この特定の関数を呼び出すために何日もチャレンジに費やしています x)
メモリ破壊のバグやヒープの問題を探してバイナリをリバースしましたが、実際には奇妙なコマンドインジェクションでした。
このバイナリはsystem()を実行する前にすべてのセキュリティ対策を講じていますが、残念ながら私たちは常にそこに入力を注入できます。
Screenshot
OK、それではバイナリの先頭からsystem関数までを順にたどって、そこに私たちの入力を注入してみましょう。
まず、バイナリは最初の引数が --help または -h かどうかをチェックし、先ほど見たメッセージを表示します。
Screenshot
次に、権限をrootに昇格させます。
Screenshot
その次に、意図しない別のバイナリを呼び出さないように、ほぼすべての環境変数をunsetします(セキュリティ対策)。
Screenshot
つまり、入力した最初の引数が "mount" の場合、この分岐に入り、与えられたフラグをチェックします。それらのフラグはスタック上に設定されます。
次に、mountの後の次のパラメータがUUID=かどうかをチェックします。ここには入りたくないので、"/dev/../tmp/;/tmp/exploit" を与えました。
Screenshot
このようにして、410行目のチェック、つまりstrncmpチェックを通過します。
/dev/ で始まらない場合、バイナリは終了するからです。
次に、私たちが提供したそのファイルに対してstat64の呼び出しがあります。 ";" という名前のフォルダを作成できることに注意してください。それがコマンドインジェクションを引き起こします。
これまでに、エクスプロイトはすでにこのファイル /dev/../tmp/;/tmp/exploit を作成していますが、これは呼び出されるエクスプロイトではありません。
Screenshot
Screenshot
そろそろ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として呼び出すことができるようになります。
Screenshot
このバイナリは、意図しない動作を軽減するために最善を尽くしましたが、いつものように、どんなものでもpwnできます。私はこのような論理バグを使ってこれを悪用するとは予想していませんでした。
次のCVEは、LPE rootにつながるメモリ破壊であってほしいです。