
# CVE-2021-4034(PwnKit)のルートエクスプロイト pkexec の領域外書き込みを悪用して、Linux システム上で権限を root に昇格させるエクスプロイトです。
PwnKit 脆弱性のルートエクスプロイト。元のレポートは こちら を確認してください。
このエクスプロイトは、対象システムの所有者の明示的な許可を得て使用してください。
libc 以外の依存関係は不要です。make を実行するだけです。
オプションなしで実行すると、エクスプロイトが実行されます:
[linux@linux ~]$ ./exploit
-----------------------------------------------------------------------------
__\ / __ __ _ __ _ __ | \ / _ ___
/ V |_ --- _)/ \ _)/| ---|_|/ \__)|_| | V |_) _/|_|
\__ |__ /__\_//__ | |\_/__) | | | \/__| |
-----------------------------------------------------------------------------
sh-5.1# whoami
root
sh-5.1#
pkexec へのパスと「from」文字セットをカスタマイズできます:
[linux@linux ~]$ ./exploit -h
...
./exploit [-c] [-h] [-f from_charset] [-p /path/to/pkexec]
-----------------------------------------------------------------------------
-c ティアダウンのみ - エクスプロイトしない
-p <path> pkexec へのパス (デフォルト: "/usr/bin/pkexec")
-f <from_charset> カスタム「from」文字セット (デフォルト: "UTF-8")
-h このメッセージを表示
GIO_USE_VFS の正体は?!ソーシャルメディアで、GIO_USE_VFS= が定義されていないと一部のエクスプロイトが失敗するのはなぜか、古いバージョンではなぜ動作するのか、と質問している人を何人か見かけました。
polkit のコミット daf3d5c2d15466a267221fcb099c59c870098e03 が原因です。
差分の関連部分は以下の通りです:
--- a/src/programs/pkexec.c
+++ b/src/programs/pkexec.c
@@ -503,6 +503,9 @@ main (int argc, char *argv[])
opt_user = NULL;
local_agent_handle = NULL;
+ /* GIO からのリモートファイルアクセスを無効化する。 */
+ setenv ("GIO_USE_VFS", "local", 1);
+
/* 正しい呼び出しを確認 */
if (geteuid () != 0)
{
このコミットより前のバージョンは、GIO_USE_VFS 変数を定義する必要なくエクスプロイト可能です。このコミット以降のバージョンは、この変数が定義されない限りエクスプロイトできません。
このコミットの目的は実はミスリードです。重要なのは変数の意味ではなく、その存在がプログラム環境にどのような影響を与えるかです。真実を知るには libc を見る必要があります。
libc におけるプロセスの環境は、このグローバル変数が指す char * の配列で表されます:
char **environ;
environ はヒープ上に存在し、時々再配置されます。どこに向かっているかもうお分かりでしょう。setenv.c のこのコードスニペットを確認してください:
#if !_LIBC
# define __environ environ
# ifndef HAVE_ENVIRON_DECL
extern char **environ;
# endif
#endif
int
__add_to_environ (const char *name, const char *value, const char *combined,
int replace)
{
char **ep;
// ... 省略
ep = __environ;
size = 0;
if (ep != NULL)
{
for (; *ep != NULL; ++ep)
if (!strncmp (*ep, name, namelen) && (*ep)[namelen] == '=')
break;
else
++size;
}
if (ep == NULL || __builtin_expect (*ep == NULL, 1))
{
char **new_environ;
/* この領域は割り当て済みなので拡張できる。 */
new_environ = (char **) realloc (last_environ,
(size + 2) * sizeof (char *));
// ... 省略
last_environ = __environ = new_environ;
}
__add_to_environ() は setenv(3) と putenv(3) の両方から呼び出され、環境変数を設定するという約束を果たします。問題の環境変数が定義されていない場合、新しいエントリ(新しい環境 key=value ペアへのポインタ)を収容するために environ を再割り当てする必要があります。定義されている場合、environ 配列のサイズは変わらないため、再割り当ての必要はありません。簡潔にするため、その部分のコードは省略しました - ぜひ自分で確認してみてください。
では、エクスプロイトに戻りましょう。ここまで読んだなら、おそらくこのエクスプロイトの背後にある手法はすでにご存知でしょう(知らない場合は元のレポートを確認してください)。空のプログラム引数(argv)を pkexec に渡すことで、環境変数をこっそり忍び込ませようとしています。argc が本当に空(プログラム名すらない)の場合、隣接する環境変数が引数と衝突します。この動作を悪用して、pkexec にターゲット実行ファイルの正規パスを環境に書き込ませます。ただし、コードのこの部分に到達する前に、次の処理が行われます:
setenv ("GIO_USE_VFS", "local", 1);
この変数が環境に存在しない場合、environ は再割り当てされ、argv と衝突しなくなります。その結果、範囲外書き込みがプログラムの環境に影響を与えず、エクスプロイトが失敗します。