最近,我一直在探索两件事:Vibe Coding 究竟能把一个人的想法推进多远,以及公开暴露的 LLM 服务正在面对哪些攻击。
于是,我把这两个问题放进了同一个项目里。
我提出需求、安全边界和验收标准,和 Codex 一起从零开发了一套原创的 LLM API 蜜罐,并把它部署到了互联网上。从架构设计、接口仿真、事件分类到报告生成,项目中的代码都围绕这次实验逐步完成。
它设置了五个诱饵 profile,分别模拟 Ollama、OpenAI-compatible/vLLM、TGI/llama.cpp、LM Studio 和 LiteLLM 常见的公开 API 形态。对外开放的端口和接口看起来都很熟悉,但背后没有任何真正的模型。它不会执行来访者提交的提示词,不会调用工具,也不会按照请求中的地址下载文件。
它只做一件事:观察公开暴露的 LLM API 会遇到什么。
从 8 月 3 日到 8 月 22 日,这套蜜罐记录了 3,815 次请求,来自 1,564 个不同的 IP。绝大多数流量只是自动化扫描,但其中也混进了路径穿越、超大请求体和潜在 SSRF 探测。
20 天已经足够看到一个清晰的现实:只要端口对公网开放,很快就会有人来确认它是谁、能做什么、有没有可以继续利用的入口。
先说说这套蜜罐
LLM Honeypot 是我借助 Codex 开发的一套原创、协议层、非执行型蜜罐,也是我近期 Vibe Coding 实验中的一个完整项目。
传统蜜罐常常伪装成 SSH、数据库或 Web 应用。这套系统选择了另一类正在快速增加的互联网资产:大语言模型 API。
我和 Codex 为五种常见 API 形态设计了不同的固定响应。扫描器访问根路径、模型列表、聊天接口或框架专用端点时,会看到一个像模像样的服务。所有请求都会经过大小限制和敏感信息脱敏,再写入事件库,由规则引擎进行确定性分类。
这里的名称代表蜜罐使用的观测分组。vLLM 是提供 OpenAI 兼容接口的开源推理引擎,并不属于 OpenAI;TGI 与 llama.cpp 也是两套独立软件。由于它们暴露的部分路径和扫描指纹高度重叠,当前版本将 TGI 与 llama.cpp 放进同一个诱饵 profile。端口只是这次部署的入口,不能单独用来判断后端软件。
这里有一条很重要的安全边界:蜜罐只模拟协议,不加载模型,不执行攻击者输入,不访问对方提交的 URL,也不调用任何工具。
它部署在与生产环境隔离的主机和网络中。因为蜜罐本身就是要接触未知攻击流量,单靠 Docker 隔离远远不够,宿主机出站、云网络、管理端口和数据存储都需要单独设防。

3815次请求里,大部分人在找什么?
在全部事件中,3,509 条属于服务枚举,占 92%。
扫描器会依次访问根路径、/v1/models、/v1/chat/completions、健康检查和不同框架的特有接口。访问顺序通常很有规律:先判断端口上是否有服务,再确认它像 Ollama、vLLM 还是其他框架,接着寻找能够生成内容或管理模型的入口。
五个诱饵 profile 收到的请求并不均匀:
- TGI / llama.cpp 合并 profile:2,052 条,占 53.8%;
- OpenAI-compatible / vLLM:892 条,占 23.4%;
- LiteLLM:374 条,占 9.8%;
- LM Studio:305 条,占 8.0%;
- Ollama:192 条,占 5.0%。

TGI / llama.cpp 合并 profile 和 OpenAI 兼容接口吸引了最多流量。它可能反映了互联网中这类接口的普及程度,也可能与扫描器使用的默认字典有关。仅凭蜜罐数据,我无法判断哪一种解释占主导,所以没有继续做归因。
来源 IP 也呈现出典型的广泛扫描特征。1,564 个 IP 中,有 1,008 个只出现过一次,占 64.5%。它们更像从整个互联网掠过的自动化探针。
但还有 556 个来源不止一次出现,151 个来源在至少 7 天里保持活跃,32 个来源横跨至少 14 天。背景噪声之中,持续复扫同样很常见。
从模型列表到推理调用
单次访问模型列表通常只能说明“有人看了一眼”。真正有意思的是,扫描器看完以后做了什么。
在报告窗口内,34 个来源向聊天、文本生成等模拟推理端点发出了 229 次请求。这些请求只触发固定的仿真响应,背后没有模型运行。
其中 215 次推理请求发生在同一来源完成根路径、健康检查或模型列表枚举后的 30 分钟内,占 93.9%。这条序列很清晰:先确认服务活着,再读取模型清单,接着逐个尝试推理。
蜜罐公布了三个虚假的模型名。后续 229 次模拟推理请求中:
qwen2.5:14b-instruct-q4_K_M:92 次,占 40.2%;deepseek-r1:32b:73 次,占 31.9%;llama3.3:70b-instruct:60 次,占 26.2%;- 3 次没有填写模型,另有 1 次请求了清单之外的
qwen2.5:7b。

三个主要模型合计占 98.3%。这并不说明攻击者天然更喜欢 Qwen、DeepSeek 或 Llama,因为这三个名字正是蜜罐刚刚展示给他们的清单。更合理的解释是:扫描器读取了服务返回的数据,再自动把发现的模型名填入下一轮请求。
换句话说,模型列表并非一个无关紧要的展示接口。它正在给后续自动化工具提供可直接消费的目标清单。
他们给“模型”发了什么?
把 /v1/responses 的兼容性探测也计算在内,一共有 268 次推理尝试。消息内容大致分成三组。
第一组是存活与兼容性测试,共 160 次,占 59.7%。常见内容很短,例如 Reply with OK.、hi、hello、Reply with the single word: verified,还有 29 字符的 say pong. [IGNORE: PR0B3]。这类消息追求的是可预测、便于机器判断的结果:只要响应格式正确,并出现预期单词,扫描器就能确认接口可以工作。
第二组更像安全边界测试。一个来源在约 14 小时内发出 50 次请求,把五类高风险问题轮流投给三个虚假模型,主题包括爆炸物、毒品制造、加密货币洗钱、暴力伤害和露骨色情。每个主题出现 10 次,Qwen 对应 20 次,DeepSeek 和 Llama 各 15 次。
这个模式很整齐:相同问题、相同参数、轮换模型、重复执行。它更像自动化的模型安全评测或越狱能力摸底,无法据此判断对方准备实施现实中的违法行为。对于真实服务,风险仍然存在——开放接口可能被第三方免费用来批量测试安全护栏,消耗算力,并寻找拒答策略较弱的模型。
第三组包含 58 次其他请求。其中一段流式请求带有较完整的 system message,要求模型判断一条用户消息是否应进入 Agent Mode,用户问题是“你是什么模型”;紧接着,同一来源又分别调用 Llama、DeepSeek 和 Qwen,发送“你是什么模型”“你好”等中文消息。这已经很像某个客户端或前端把发现的服务当成真实模型提供方进行连接测试。
推理之后,他们又去了哪里?
34 个调用过推理接口的来源中,21 个在首次调用后的 30 分钟里继续发送推理请求;4 个重新访问模型列表;2 个转向模型管理相关路径;13 个在这 30 分钟内没有再产生后续事件。
最活跃的推理来源表现得尤其机械。它先在 /v1/chat/completions 上对三个模型各测试 13 次,随后又转向较新的 /v1/responses,对三个模型各测试 13 次,总计 78 次。蜜罐没有实现 /v1/responses,39 次请求全部返回 404,对方仍然完成了整套轮询。
这更像一套长期运行的兼容性扫描器:它不满足于“接口能不能回答”,还要确认服务支持 Chat Completions 还是 Responses API、哪些模型可用、流式格式是否符合预期。
另一个来源只做了一件事:通过 /api/generate 给三个模型分别发送 7 次 hi,共 21 次。它没有探索复杂能力,只是在重复验证最小生成链路。
这些行为说明,公开 LLM 服务的枚举已经开始从“识别端口”走向“消费模型清单、轮询模型、切换 API 版本、验证返回格式”。与传统端口扫描相比,它更接近一次自动化的产品验收。
把OAST地址塞进模型拉取接口
8 月 19 日,一个来源向 Ollama 伪装服务的 /api/pull 提交了远程模型地址。域名指向 OAST 服务,请求中还带有 insecure: true。
OAST 常用于验证目标是否会主动连接测试者控制的服务器。放在模型拉取接口里,这个请求很像一次 SSRF 或数据外带能力探测:攻击者想知道服务器会不会按照输入地址主动发起连接。
蜜罐没有访问这个 URL。这条日志说明有人进行了测试,无法说明出站连接真实发生。
如果一套真实系统允许用户指定模型下载地址,风险会迅速扩大。攻击者可能借此访问内网服务、云元数据地址和环回接口,或者诱导服务器下载受控文件。
更稳妥的设计包括:只允许经过验证的模型仓库;拒绝私网、环回、链路本地和保留地址;防止 DNS 重绑定;下载组件不持有敏感凭据;将模型下载放进出站受限的隔离网络。
传统Web攻击也会落到LLM端口
除了围绕模型下载和推理接口展开的探测,这套蜜罐也收到了路径穿越和超大请求体测试。它们并非 LLM 独有,更接近互联网公开服务每天都会面对的传统 Web 攻击。
将它们放在报告中仍然有价值,因为真实的 LLM 服务前面通常还有 CDN、反向代理、API 网关和应用框架。攻击者不需要先确认后端运行了哪一种模型,就可以直接检查这些通用组件。
读取 /etc/passwd 和 .env
系统记录了 24 次路径穿越与敏感文件探测,规则严重度为 7,置信度为 0.96。
其中 20 次尝试通过多层 ../ 读取 /etc/passwd,另外 4 次指向 .env。这些请求同时出现在 TGI/llama.cpp、OpenAI-compatible/vLLM 和 LiteLLM 的诱饵 profile 上。
这说明来访者没有把赌注押在某个已经确认的框架漏洞上。他们更像在批量检查反向代理、URL 解码、路径规范化和静态文件映射是否存在疏漏。
四个来源多次执行了这类探测,其中三个跨越多个日期反复出现。所有请求最终都收到 404,日志中没有文件内容泄露,也没有看到成功之后的深入活动。
因此,这些记录只能证明攻击尝试发生过,不能证明系统已经被入侵。
对真实的 LLM 网关来说,一个请求在 CDN、Nginx、应用框架和上游服务之间可能经历多次解码与重写。入口看到的路径安全,不代表传到最后一层时仍然安全。
连续发送10 MiB请求体
8 月 16 日,同一个来源在不到一秒内向 TGI/llama.cpp 合并 profile 发送了两次约 10 MiB 的 POST 请求,两个请求体只相差 1 字节。
这种整齐的边界值很像请求体大小测试,也可能是拒绝服务之前的探测。两次请求都被 413 拒绝,说明限制确实生效了。
但“返回 413”还不够。
真正需要确认的是,请求在哪一层被拒绝。如果反向代理先把 10 MiB 内容完整接收、缓冲甚至交给解析器,再由应用返回 413,网络、TLS、内存和连接资源仍然会被消耗。理想做法是在最外层尽早丢弃,并把单一来源短时间连续触发 413 设为告警条件。

如果你也在公网部署LLM API
这 20 天的数据给出了几条很具体的建议。
第一,尽量不要把模型接口直接暴露给整个互联网。将访问收敛到 VPN、零信任接入、固定来源或经过认证的 API 网关,并为 IP 和 API Key 同时设置请求速率、并发数与 token 配额。
第二,梳理所有会让服务器主动连接外部地址的能力。模型下载、插件安装、工具调用和 webhook 都需要统一的出站策略,不能只在应用代码里做一次字符串判断。
第三,在 CDN、WAF 或最外层反向代理限制请求体和连接速率。应用返回 413 只是结果,还要观察请求体大小分布、连接持续时间、解析前拒绝数以及各接口的并发占用。
第四,为路径处理保留足够的排查信息。原始路径、规范化路径的哈希、解码层数、上游路由结果和 request-id,都能帮助定位双重解码或重写问题。日志中不要保存令牌和完整敏感参数。
第五,把高严重度事件从周报中提前拆出来。本次只有 27 条事件的严重度达到 5 以上,占总量 0.7%,却包含了窗口内最有价值的攻击信号。它们应该在出现后立即按来源、服务类型和攻击手法聚合通知。
20天之后,我对“开放一个端口”有了新的感受
每天 116 到 321 次请求,规模谈不上惊人。真正让我在意的是这些流量来得很自然。
没有宣传,没有域名,也没有任何真实模型能力。五个公开端口依然吸引了 1,564 个来源。有人只是经过,有人读取模型清单后逐个调用,有人切换 Chat Completions 与 Responses API,还有人批量测试模型的安全边界。
公开的 LLM API 已经进入互联网自动化扫描的常规视野。部署者面对的也不只是提示词注入。模型清单泄露、推理配额滥用、接口兼容性探测、安全护栏批量测试、模型供应链和出站网络控制,都已经出现在真实流量里。
这套蜜罐还会继续运行。当前数据只覆盖 19 天 20 小时,还不足以代表完整自然月。下一阶段,我想继续观察这些来源是否会形成更稳定的扫描周期,也会加入高严重度事件的即时摘要和更完整的活动关联。
互联网上总有人在敲门。
知道他们敲了哪扇门、接下来又去了哪里,防守才有机会更早一步。