[toc]
脆弱性ID: CVE-2021-3560
脆弱性スコア:
脆弱性製品: linux PolKit (polkitd)
影響範囲: ソースコード 0.113 で導入;https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
利用条件: linux ローカル;dbus + polkitd が存在
利用効果: ローカル権限昇格
ソースコード入手先:
apt source accountsserviceapt source dbusubuntu デフォルトイメージ 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
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 に設定しています:
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 の脆弱性トリガーパスを分析します。
まず main:
polkit-0.113\src\polkitbackend\polkitd.c : 155 : main
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); //ループ開始
··· ···
··· ···
}
org.freedesktop.PolicyKit1 というバス名を登録し、3つのコールバック関数があります。特に on_bus_acquired に注目します。これはバスにアクセスされたときに呼び出されます。
on_bus_acquired 関数では、直接 polkit_backend_authority_register という型登録関数を呼び出します。
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
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 です: