公网暴露LLM推理端点被僵尸网络NadMesh扫描窃取密钥事
公网暴露的 LLM 推理端点正在成为僵尸网络的获利目标。
近期 NadMesh 僵尸网络通过批量扫描互联网上的 8080、8000、443 等端口,发现暴露的 OpenAI 兼容 API 端点后,直接调用其 /v1/chat/completions 接口消耗额度,并且从环境变量、日志文件或 /etc/.env 路径中窃取 API 密钥。
对于所有部署了 vLLM、Ollama、FastChat 或自建推理网关的团队来说,这是一次必须重视的安全警告。
本文将从事件原理、排查方法和加固步骤三个角度,帮你用 30 分钟内完成一次自查与修复。
NadMesh 攻击链剖析:扫描、探测、窃取三连
NadMesh 并不是传统的挖矿木马,而是主要劫持 GPU 资源和大模型 API 密钥的黑客团伙。
它的攻击路径非常清晰:
- 端口扫描:使用 Zmap 或 Masscan 对公网 IP 段进行大规模扫描,重点识别 80、443、8000、8080、3000、5000 端口。
- 端点识别:对开放端口发送简单的 HTTP 请求,检测路径
/v1/models、/v2/models、/api/chat等。如果返回 JSON 且包含model字段,就判定为 LLM 推理端点。 - 密钥窃取:尝试读取常见敏感文件:
/etc/.env、.env、/app/config.py、/proc/self/environ;或者用抓包方式从请求头Authorization中截获明文密钥。 - 资源滥用:窃取密钥后,攻击者用你的账单资源生成垃圾文本、训练自己的模型,甚至把密钥转卖到黑市。
攻击者最喜欢两类部署:一是没有身份验证的裸端点;
二是把 API 密钥硬编码在环境变量或容器启动命令里的服务。
如果你把推理服务直接映射到 0.0.0.0,且未加任何反向代理认证,就相当于把你的钱包口令贴到了公网门口。
自查三件事:你的端点是否已经暴露
先别慌,按照下面三步检查你的服务器和云厂商控制台。
1. 检查公网监听端口
登录服务器执行 ss -tlnp | grep -E '8080|8000|443|80',查看监听地址是否为 0.0.0.0 或 ::。
如果地址是 127.0.0.1,说明服务只在本机通信,相对安全。
另外,用 curl -X POST http://服务器公网IP:端口/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"test","messages":[{"role":"user","content":"hi"}]}' 试探,如果返回 400 或模型不存在提示,说明端点可被公网直接访问;
如果返回超时或连接拒绝,则外部无法触达。
2. 检查云安全组和防火墙
登录阿里云、腾讯云或 AWS 控制台,找到运行推理服务的 ECS/轻量服务器安全组,查看入方向规则。
如果有 0.0.0.0/0 或 ::/0 且端口为 8000、8080、5000 等,就存在风险。
同时执行 sudo iptables -L -n,确认云防火墙外是否还有本机规则放行了这些端口。
3. 检查日志中的异常调用
对于使用 Nginx 或指网关的部署,直接查看访问日志:
sudo grep -i "nadmesh" /var/log/nginx/access.log
sudo grep -E "chat/completions.*200" /var/log/nginx/access.log | wc -l
大量来自非预期 IP 的 200 响应、连续调用次数上千次,并且请求体有明显乱码或重复文本,均说明端点可能已被扫描利用。
硬核加固:让扫描器看不见、打不进、偷不走
下面操作按风险从高到低排列,建议全部执行。
方案一:为推理服务添加反向代理和 Basic Auth
用 Nginx 作为入口,只让带正确认证头的请求访问后端的 LLM 服务。
以默认配置为例,在 /etc/nginx/conf.d/llm.conf 中写入:
server {
listen 8443 ssl;
server_name your-gpu-server;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
生成账号密码文件:
sudo apt install apache2-utils -y
sudo htpasswd -c /etc/nginx/.htpasswd llmadmin
之后,任何一个不带 Authorization 头的外部请求都会被 Nginx 拒绝,NadMesh 扫描器在第一步就止步。
方案二:云安全组只放行办公 IP 或 VPN 网段
在安全组控制台,修改入方向规则,将 8000、8080 等推理端口的源 IP 限制为你公司的出口 IP。
如果团队人员分散,建议使用 WireGuard 或 Tailscale 等组网工具,只放行虚拟局域网网段,比如 100.64.0.0/10。
这样哪怕 Nginx 配置失误,外部扫描也无法建立 TCP 连接。
方案三:密钥集中管理与轮换
不要继续把 OpenAI API 密钥写在环境变量里。
改用 vault 或云厂商的密钥管理服务(KMS),并在服务启动时通过 API 读取。
同时立即打开 OpenAI 控制台的 API Keys 页面,检查是否出现了未知密钥;
如有可疑,马上撤销并创建新密钥,并在应用配置中更新。
方案四:网关层强制校验真实密钥格式
如果服务面向外部开发者,需要让网关程序先校验请求中的 API Key 是否符合你的签发规则(比如固定前缀 ctx- 加随机串),再转发到 LLM 后端。
在 Python FastAPI 中可拦截 Authorization 头:
@app.middleware("http")
async def check_key(request: Request, call_next):
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer ctx-"):
return JSONResponse(status_code=401, content={"detail": "Invalid key"})
return await call_next(request)
若后端是 vLLM,可参考该中间件在高可用网关中统一注入。
验证结果与后续监控
完成上述步骤后,需要验证攻击面是否已收敛。
- 从公网环境执行
curl -I http://你的IP:8000/v1/models,预期返回401 Unauthorized或连接超时。 - 从办公网 IP 执行同样的请求,预期返回
200 OK。 - 访问 Nginx 错误日志,观察是否有大量
401记录。如果 5 分钟内出现上百次来源不同的 401,说明扫描仍然存在,但攻击已无法进入。 - 登录 OpenAI 或模型厂商后台,确认没有新的未知消耗记录。
另外建议部署日志审计,使用 goaccess 或免费的 Loki + Prometheus 实时分析访问日志,发现异常 IP 后自动封禁。
常见疑问与避坑补充
为什么 Nginx 加了 Basic Auth,外部还是能访问? 很可能是你直接暴露了后端端口,而 Nginx 只是无门槛反代。
请务必在安全组内关闭后端端口的外部访问,并只允许 127.0.0.1 访问后端。
已经泄露的密钥如何处理? 立即撤销并重新生成,然后检查云账单中是否有异常调用扣费,同时排查是否有持久化后门,比如可疑的 cron 任务或外连脚本。
公网 LLM 端点一定不能对外开放吗? 如果业务需要被调用,至少要使用 mTLS、OAuth2 或网关级别的密钥签名。
每次攻击事件都证明:裸奔的 AI 接口就是黑产的提款机。
安全没有“一次加固永远无忧”的捷径。
建议每周检查一次安全组规则和密钥用量,订阅安全情报源,在发现新变种扫描时能够快速响应。
现在就从检查监听端口开始,把 LLM 服务的防火墙补上。