Dify未授权访问批量扫描频发:三步修复方案与自查指南
最近安全圈里讨论得最凶的一件事就是自托管的Dify实例被批量扫描控制台裸奔、API端点无鉴权、后台被人塞了一堆来路不明的Bot。Dify现在是大模型应用开发里很顺手的一站式平台不少人拿它搭知识库、做企业内部AI助理但正因为部署门槛低很多实例的默认配置直接暴露在公网成了别人眼里的肉鸡。这标题既然挂着“紧急预警”我直接把最近实测过的一套修复方案整理成文先自查暴露面再做三步关键修复最后处理善后痕迹。无论你是个人开发者还是小团队运维只要手上有Dify实例都建议按顺序过一遍。1. 漏洞背景Dify未授权访问的本质与影响1.1 什么是未授权访问为什么对Dify伤害特别大未授权访问字面意思就是攻击者不需要提供任何身份凭证就能直接访问系统里的敏感功能或数据。对Dify这类应用来说问题就非常严重了。一个典型的Dify实例里有工作流编排、知识库文件、Prompt模板、API Key、对话日志、用户账号体系这些东西只要任何一个端点没做鉴权就等于把整套AI应用的核心资产摊在公网上。有人可能觉得我数据集里都是些公开文档聊天记录也没什么机密被访问一下能怎样。真不是这样。攻击者拿到Dify后台之后可以做四件非常实际的事情第一遍历你配置过的所有大模型供应商Key用你的配额跑自己的任务账单算在你头上第二直接读取知识库里的全部原始文档哪怕你本意是内部资料对一个知识库型应用来说这等于数据裸奔第三篡改或复制你的工作流和Prompt把你的应用整体搬走或者反向投毒第四通过你已经配置好的模型能力做恶意输出、钓鱼文案批量生成最终追溯源头还是你的IP和域名。更麻烦的是Dify的很多API路径是高度可猜测的。控制台相关接口一般在/console/api下应用服务的对话接口是/v1/chat-messages而老版本里还有一批未做严格校验的接口。攻击者根本不需要多高深的技术拿扫描工具全段探测只要发现访问响应不是401而是200直接就进来了。这就是标题里说“频发”的原因不是某个漏洞多复杂而是暴露的实例太多、利用门槛太低。1.2 为什么自托管Dify实例特别容易中招很多自托管用户部署Dify用的是官方提供的docker compose方案这个方案默认情况下会把所有端口绑定到0.0.0.0然后云服务器安全组又习惯性放行所有入站流量。两个默认值叠加在一起服务就直接暴露在公网了。官方文档确实写了要配置反向代理和TLS但很多教程拿着compose文件一把梭根本没人提收口的事环境装好了就立刻开始建应用谁能想到第一步就埋了雷。另一个隐患是默认密钥。本地部署的.env文件里会生成一个SECRET_KEY如果你是从旧版本一路升级上来或者从别人分享的配置模板里复制过来的这个Key很可能长时间没换过。它用来签名Cookie、生成内部Token、加密一些敏感数据一旦泄露攻击者就可以伪造会话。配合未鉴权的管理接口等于连爆破都省了。还有一个大家忽视的点自托管版没有平台方帮你做安全兜底。Dify云服务由官方统一维护但自托管实例的防火墙、鉴权、访问控制、版本更新全部落在使用者自己头上。很多小团队或个人的习惯是容器起来之后就不管了版本常年不升级安全补丁迟迟不跟进等到被扫描到的时候别人已经用旧版本的已知问题进了后台。2. 修复前必做的三个自查动作在动手改配置之前建议先花十分钟搞清楚两件事你的实例到底有没有暴露在公网以及被暴露的现状到了什么程度。不做自查就直接改配置很容易出现“改完觉得安全了其实入口还开着”的情况。2.1 自查一确认实例是否已经暴露在公网先看监听状态。在部署Dify的服务器上执行ss -tlnp | grep -E 80|443|3000|5001如果看到类似0.0.0.0:80或0.0.0.0:3000的监听说明服务在等待所有网卡的连接。接下来用另一台不在同一内网的机器或者直接用手机流量访问http://你的服务器IP如果页面能正常加载出Dify的登录页或控制台那就已经暴露了。还有一种情况是安全组层面挡了但本机防火墙没开。很多云服务器默认安全组是全放行的Ubuntu的ufw和CentOS的firewalld默认又是关闭状态这时候即使本机没监听端口安全组也把流量放进来了。建议顺手检查一下sudo ufw status sudo firewall-cmd --list-all如果这两个命令显示firewall是inactive或没有规则就该意识到当前实例的访问控制几乎是空白的。再配合一个从外部视角的检测直接在本地电脑上访问服务器IP的80和443端口这是最直观的验证方式。2.2 自查二摸清当前版本和敏感配置现状确认自己的Dify版本和配置状态需要进入部署目录。如果是docker compose部署先找到docker-compose.yaml所在的目录然后执行docker ps --format table {{.Names}}\t{{.Image}}\t{{.Ports}}这样能看到api、web、worker各容器的镜像标签标签里的版本号就是当前运行版本。接下来看.env文件里的关键项grep -E SECRET_KEY|GUEST|INIT_PASSWORD|API____KEY .env重点关注三处一是SECRET_KEY是不是一个高强度随机值如果看起来像your-secret-key这种占位符基本等于没有二是是否存在游客模式相关的开关如果开着说明没登录的人也能访问部分功能三是有没有配置过初始管理员密码的强校验选项。这些信息决定你第三步要改哪些东西。2.3 自查三检查是否有异常账号、应用和API Key如果实例已经暴露了一段时间光看配置还不够要查一下数据库里有没有被塞进来的脏数据。Dify默认使用PostgreSQL执行docker compose exec db psql -U postgres -d dify先看用户表SELECT id, email, status, created_at FROM users ORDER BY created_at DESC LIMIT 50;重点看有没有近期批量注册的陌生邮箱尤其那些域名看起来像临时邮箱的。接着看应用列表和API Key列表SELECT id, name, created_at FROM apps ORDER BY created_at DESC LIMIT 100; SELECT id, type, last_used_at FROM api_tokens ORDER BY last_used_at DESC NULLS LAST LIMIT 100;如果发现有自己从未创建过的应用或者API Key的使用时间在自己不知情的时间点那说明已经被访问过了。这一步的自查结果直接决定第三步是只做配置修复还是需要做一次全量清理。3. 三步完成关键修复市面上很多安全文章喜欢罗列一堆参数看起来严谨实际操作时反而不知道该先做哪件事。我按止损优先级把Dify的未授权访问修复拆成三步第一步关入口第二加固身第三步清善后。顺序不能乱因为入口不关后面改什么配置都可能被再次绕过。3.1 第一步把服务从公网剥离开来只通过网关访问这一步的核心思路是让Dify的原有端口不再直接暴露给外部网络所有外部流量都经过一层由你控制的代理网关。容器本身不知道外面谁是好人坏人但Nginx或Caddy可以在更靠外的位置做访问控制。先修改docker-compose.yaml里的端口映射。打开文件后找到web服务的ports段落默认大概长这样services: web: ports: - 80:80把它改成只绑定到本机回环地址services: web: ports: - 127.0.0.1:80:80如果服务器上已经装了Nginx直接让它监听公网端口并把请求转发到http://127.0.0.1:80。保存后重建容器docker compose up -d这时候再从外网访问原来的IP应该已经连不上Dify了。但光这样还不够因为云安全组如果放行了80流量还是会到Nginx。所以继续在Nginx配置里加上访问控制我用一个基本的server块示例server { listen 443 ssl http2; server_name dify.example.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; auth_basic Restricted; auth_basic_user_file /etc/nginx/.htpasswd; location / { proxy_pass http://127.0.0.1:80; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } }auth_basic就是HTTP基本认证相当于给整站加了一层“前置门锁”密码用htpasswd工具生成sudo apt install apache2-utils -y htpasswd -c /etc/nginx/.htpasswd difyadmin如果你希望更精细一些还可以用IP白名单替代或叠加Basic Auth。我自己常用的做法是办公网段直接放行其余网段要求Basic Auth既方便内部使用又挡住了公网扫描器。提示改完端口映射和Nginx配置之后一定要从外部网络实际访问一次确认原来的端口已经无法到达。很多人在这一步只改了配置没重启容器等于白做。3.2 第二步加固Dify自身配置强制轮换密钥并升级版本网关层是外部防护Dify自身的配置是内部防线。第二步的核心是把所有该换的密钥全部换掉把默认弱配置全部纠正同时把版本升级到当前已修复问题的最新版。先更新.env里的SECRET_KEY生成一个新的高强度随机值python3 -c import secrets; print(secrets.token_hex(32))把输出填入.envSECRET_KEY随机生成的那一串接下来检查并关闭游客模式。不同小版本里这个开关的名字略有差异常见的是GUEST_MODE或ENABLE_GUEST相关键设置为false即可。如果.env里找不到就去官方文档里查当前版本对应的变量名。同时检查初始化密码相关的配置如果之前用的是默认初始化密码现在必须强制修改一次。改完配置后重建容器让配置生效docker compose up -d然后带着新的配置重新初始化管理员账号。如果原本的管理员密码已经被别人动过这一步尤其重要。执行后打开控制台进入账号管理把所有不认识的账号全部停用或删除给保留的账号重新设置强密码并开启二步验证。API Key也要全部重置。进入每个应用的“访问API”页面把旧的Key全部删除生成新的Key然后更新你所有业务系统里的密钥。这一步很麻烦但必须做因为旧Key可能已经被扫描器抓走并用于调用你的模型额度。最后升级Dify版本。在部署目录下执行git pull docker compose pull docker compose up -d如果你在compose文件里做过自定义修改git pull可能会提示冲突建议升级前先备份整个部署目录。版本升级之所以重要是因为很多未授权访问问题已经在后续版本里修复了单靠外部网关挡不了所有未知接口版本本身必须有对应的安全补丁。3.3 第三步清理痕迹确认没有遗留后门配置修好了不代表事情结束了。如果之前已经被人进过后台数据库里很可能留着他们创建的账号、应用、API Key甚至可能有人改了Prompt模板做钓鱼用。第三步是用审计的方式把脏东西捞出来清干净。先看容器日志找出最近的访问来源docker compose logs api --since 24h | grep -E console/api|v1/chat-messages | awk {print $NF} | sort | uniq -c | sort -nr | head -20如果看到大量来自陌生IP的/console/api访问结合前面数据库里的异常记录基本可以判断被访问过。把这些IP记下来在防火墙里永久封禁sudo ufw deny from 1.2.3.4数据库里清理异常用户和关联数据。只删除users表里的记录通常会因为外键约束失败需要先清理子表数据。经验操作是把可疑用户查出来后记录其ID依次删除messages、conversations、api_tokens、workflow中与该用户关联的数据最后再删users记录。为安全起见动手前先做一次全量备份docker compose exec db pg_dump -U postgres dify dify_backup_$(date %F).sql清理完数据库后检查一下容器里有没有被挂载的异常目录或新增的定时任务。攻击者有时会利用未授权接口写入计划任务在宿主机上执行crontab -l看看有没有可疑条目再检查一下/tmp和/var/spool/cron下有没有奇怪的文件。最后把日志和监控接起来别让下一次暴露再次裸奔。最朴素的做法是把Nginx的access log丢到ELK/Loki里或者在服务器上写一个定时脚本每十分钟扫描一次/console/api和/v1路径的访问来源有陌生IP就告警。下面是几个关键配置的检查清单直接对着看就行检查项合规状态处理方式80/3000端口监听地址必须只监听127.0.0.1修改docker-compose.yaml的ports外部访问是否经过反代必须是Nginx/Caddy网关配置代理并启用TLS是否启用Basic Auth或白名单必须至少启用一项配置访问控制SECRET_KEY强度必须是随机高熵值重新生成并更新.env游客模式必须关闭设置对应环境变量管理员账号仅保留自己人清理异常用户并改密码API Key全部重新生成逐个应用更新Dify版本更新到最新版git pull compose pull日志监控能看到访问来源接入日志系统或定时脚本4. 常见问题与排查技巧实录4.1 后台已经有人建了未知应用为什么删不干净这个我实际遇到过。表面上是删了一个恶意应用但过几天又出现新应用原因基本是两个要么管理员账号密码没有改攻击者还在通过正常登录方式进来要么你遗留了旧的API Key攻击者通过API直接创建应用。所以处理顺序一定要正确先重置所有账号和密钥再清理脏数据最后再封掉来源IP。如果顺序反了这边删那边建永远清不完。另一个常见原因是删除应用时没有清理关联知识库。Dify的应用和知识库之间有引用关系直接删应用可能留下孤立数据建议进入知识库页面把可疑内容一并删除。删除前先备份数据库避免误删自己正常的数据。4.2 修复后原有API Key全部失效业务报错怎么办这基本是预期内的结果因为旧Key已经不可信。业务侧会有一段时间的适配期需要批量替换所有客户端环境变量里的Dify API Key。如果应用数量多建议先做一份映射表应用名、旧Key、新Key、调用方负责人然后按业务优先级逐个替换。替换完成后重点测试一个消息流接口和一个文件上传接口确认新Key权限正常。4.3 反代之后出现400、413或WebSocket断连这个问题经常出在Nginx配置缺失上。400通常是proxy_set_header Host没设置Dify收到的请求头不带域名导致校验失败413是上传知识库文件时client_max_body_size太小默认1M肯定不够建议设置到100M以上WebSocket断连则是缺少Upgrade相关请求头。把第一步配置里那两行proxy_set_header Upgrade和Connection upgrade加上一般就能解决。4.4 公网封了但内网其他容器会不会横向访问有这个担心是对的。容器之间在同一个Docker网络里互访是不需要经过宿主防火墙的。如果你的服务器上还跑了其他业务容器而这些容器被攻破攻击者很可能会从内网直接访问Dify的端口。更稳妥的做法是把Dify的关键服务放入独立的Docker网络只在网关容器上开放对外映射。或者更进一步在内网侧也加一层访问控制例如通过云平台的安全组限制只有指定跳板机才能访问该服务器。4.5 升级之后原有应用数据还在吗在。Dify的数据都存在PostgreSQL和向量数据库里升级容器不会主动清空数据。但我见过有人在升级时直接用docker compose down -v把卷一起删了这会把数据全部清掉。正确的升级过程是docker compose pull加docker compose up -d绝对不要带-v参数。升级前备份数据库是我反复强调的事尤其是你已经发现自己被扫过的情况下备份意味着你随时有后悔药。5. 后补的一些经验这几步做完之后我的习惯是把每次修复过程整理成简短的运维记录什么时候发现暴露、改了哪些配置、换了哪些Key、封了哪些IP。记录看起来不起眼但下次再出问题时会省很多时间。Dify这类平台以后可能还会出现新的安全问题但只要你把网关层、应用层、版本层这三道防线都立住绝大多数批量扫描到你这儿就会直接跳过。最后再分享一个我自己的小技巧修复完成后不要急着把旧日志清掉留着至少一个月。因为有些攻击者会在你修复前就抓走过知识库内容等到数据泄露事件真正暴露出来时旧日志能帮你判断泄露时间窗口和数据范围。如果你有条件尽量在网关层记录完整的请求体和响应体大小这样后续做取证时有据可查。安全这件事从来都是“谁暴露谁负责”动手越早损失越小。