Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2021-4034 — Detailed write-up and proof-of-concept exploit for CVE-2021-4034 (PolKit pkexec local privilege escalation), including a Docker lab environment for hands-on analysis and debugging. | Kitploit
Tools/GitHubGitHub/chenaotian/cve-2021-4034
Privilege EscalationVulnerability AnalysisExploitationPenetration TestingLearning & EducationBinary ExploitationLabs & Practice
GitHubchenaotian/cve-2021-4034

CVE-2021-4034

Detailed write-up and proof-of-concept exploit for CVE-2021-4034 (PolKit pkexec local privilege escalation), including a Docker lab environment for hands-on analysis and debugging.

View Repository
123164 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2021-4034 PolKit Local Privilege Escalation Analysis

[toc]

Vulnerability Overview

Vulnerability ID: CVE-2021-4034

Vulnerability Score:

Affected Product: linux PolKit (pkexec)

Affected Versions: Versions from 2009 to present (current 0.105) Reference http://its.dlut.edu.cn/info/1054/78309.htm

Exploit Conditions: linux local; pkexec is a suid file with execute permission

Source Code: apt source policykit-1

​ or https://launchpad.net/ubuntu/bionic/+package/policykit-1

Docker Environment

Docker Environment: chenaotian/cve-2021-4034

I built a docker that provides:

  1. A source-debuggable pkexec compiled by myself
  2. glibc with debug symbols (seems useless?)
  3. gdb and gdb plugins pwngdb & pwndbg (seems unnecessary)
  4. The exp in the debug environment

Everything is in /root/ directory:

image-20220126183638493

  • The exp directory contains the exp and run.sh. You can directly su test to switch to test user and run it.
  • glibc-2.27 is the glibc source directory, probably not needed, but convenient for gdb source debugging when needed
  • polkit-0.105 is the policykit source package

Start the docker:

docker run -d -ti --rm -h cvedebug --name cvedebug --cap-add=SYS_PTRACE chenaotian/cve-2021-4034:latest /bin/bash

Test the exp:

cd ~/exp/CVE-2021-4034/
./run.sh
su test
./exp
whoami

Vulnerability Principle

The affected product is the pkexec command under polkit. pkexec is similar to sudo in that it allows us to execute commands as another user (usually root). Use dpkg to check which package pkexec belongs to:

dpkg -S /usr/bin/pkexec

image-20220126152839307

Then get the source package (also available in my docker), and compile a debuggable version from the source for debugging.

Vulnerability Trigger Point

The trigger principle is very simple

/polkit-0.105/src/programs/pkexec.c : 386 main

int
main (int argc, char *argv[])
{
    
  ··· ···
  ··· ···
      
  /* This loop iterates over user input parameters and sets values based on different inputs.
   * But the problem is that the loop starts at index 1, without considering the case where the user provides no parameters.
   */
  for (n = 1; n < (guint) argc; n++) 
    {
      if (strcmp (argv[n], "--help") == 0)
        {
          opt_show_help = TRUE;
        }
      ··· ···
      else //If it's an unrecognized parameter, break out; this means the parameter is the command to execute
        {
          break;
        }
    }

  ··· ···

  g_assert (argv[argc] == NULL);
  path = g_strdup (argv[n]); //Get the specific command string
  if (path == NULL)
    {
      ···
    }
  if (path[0] != '/')
    {
      /* g_find_program_in_path() is not susceptible to attacks via the environment */
      //This function searches for the absolute path of the command based on the PATH environment variable
      s = g_find_program_in_path (path); 
      if (s == NULL)
        {
          ···
        }
      g_free (path);
      argv[n] = path = s;//Write back the absolute path to the command line argument
    }
  ··· ···
  ··· ···

Based on my comments in the code:

  1. First, the main function sets some variables based on user command line arguments, but the for loop starts at 1, meaning it assumes we will provide at least one argument (the command for pkexec to execute).
  2. If it matches a command line argument that does not start with --, it considers that argument as the command to execute with pkexec, and breaks out of the loop to continue with subsequent logic.
  3. It calls g_find_program_in_path to search for the absolute path of the command. g_find_program_in_path searches for the absolute path of the passed argument (command) based on the PATH environment variable. For example, passing cat returns /bin/cat.
  4. The returned absolute path is written back to the position of that command line argument. (Think of it as converting the command to the absolute path of the corresponding file.)

Although it's easy to understand, the problem is:

  1. When a Linux binary runs, the command line arguments argv[] and environment variables environ[] are placed at the bottom of the stack, and argv[] and environ[] are contiguous. The last item in argv[] is null.

    image-20220126162140802

  2. If pkexec is started from the command line without any other arguments, then argv[0] is "pkexec", and argv[1] is \x00 – no problem. But if pkexec is started with execve without any other arguments, then argv[0] is \x00, and argv[1] points to the environment variable! When reading argv[1], it will out-of-bounds read environ[0].

    Starting pkexec directly from command line: argc is 1, argv[0] is the path to pkexec:

    image-20220126162616203

    Starting pkexec with execve: argc is 0:

    image-20220126162804529

What impact does this have? When started with execve without any other arguments, argv[] length is 0, so argv[1] equals environ[0]. Then the logic analyzed above becomes: get the value of the first environment variable, and search for its absolute path in the PATH environment variable. If found, write it back to the first environment variable. The exploitation method is as follows:

Vulnerability Exploitation

First, it must be clear that pkexec is a privileged (suid) file:

image-20220126161324831

How can we use environment variables in a privileged file to cause trouble? First, understand a small detail:

A Small Detail

The Linux dynamic linker ld-linux-x86-64.so.2 clears sensitive environment variables when executing a privileged program:

Function _dl_non_dynamic_init: glibc-2.27/elf/dl-support.c : 307

void
_dl_non_dynamic_init (void)
{
  ··· ···
  ··· ···

  if (__libc_enable_secure) // In privileged mode
    {
      static const char unsecure_envvars[] =
	UNSECURE_ENVVARS
#ifdef EXTRA_UNSECURE_ENVVARS
	EXTRA_UNSECURE_ENVVARS
#endif
	;
      const char *cp = unsecure_envvars;

      // Loop to clear (unset) all dangerous environment variables in the list
      while (cp < unsecure_envvars + sizeof (unsecure_envvars)) 
	{
	  __unsetenv (cp);
	  cp = (const char *) __rawmemchr (cp, '\0') + 1;
	}

#if !HAVE_TUNABLES
      if (__access ("/etc/suid-debug", F_OK) != 0)
	__unsetenv ("MALLOC_CHECK_");
#endif
    }
··· ···
··· ···
}

The dangerous environment variable list UNSECURE_ENVVARS is defined as:

glibc-2.27/sysdeps/generic/unsecvars.h : 10

Download Tool