CVE-2025-3248 Langflow 未经身份验证的远程代码执行漏洞分析报告
Langflow 1.3.0 之前的版本因其代码验证接口 (/api/v1/validate/code) 对用户输入不当使用 exec() 函数,存在一个允许未经身份验证的攻击者执行任意代码的严重漏洞 (CVE-2025-3248),该问题已在新版中通过添加身份验证的方式修复。
1. 概述
CVE 编号: CVE-2025-3248 漏洞名称: Langflow AI - 未经身份验证的远程代码执行 受影响版本: Langflow 1.3.0 之前的版本 漏洞类型: 代码注入 (Code Injection) 严重性: 严重 (Critical)
Langflow 1.3.0 之前的版本在 /api/v1/validate/code 端点中存在一个代码注入漏洞。未经身份验证的远程攻击者可以通过发送精心构造的 HTTP 请求来执行任意代码。
2. 漏洞代码分析 (Langflow < 1.3.0)
2.1. 端点定义 (src/backend/base/langflow/api/v1/validate.py)
在受影响的版本中,/api/v1/validate/code 端点负责接收用户提交的代码并进行验证。其核心逻辑如下:
1
2
3
4
5
6
7
8
9
10
11
12
# langflow-1.3.0/src/backend/base/langflow/api/v1/validate.py
@router.post("/code", status_code=200)
async def post_validate_code(code: Code, _current_user: CurrentActiveUser) -> CodeValidationResponse:
try:
errors = validate_code(code.code) # 直接将用户代码传递给 validate_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
此端点直接将用户在 code 参数中提交的字符串传递给 langflow.utils.validate.validate_code 函数进行处理,并且在处理过程中没有进行充分的输入验证或沙箱化。
2.2. 核心漏洞点 (src/backend/base/langflow/utils/validate.py)
validate_code 函数的目的是解析和验证用户提供的 Python 代码。然而,在处理函数定义时,它使用了 exec() 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# langflow-1.3.0/src/backend/base/langflow/utils/validate.py
def validate_code(code):
# ... (省略了AST解析和导入验证部分)
# Evaluate the function definition
for 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: BLE001
logger.opt(exception=True).debug("Error executing function code")
errors["function"]["errors"].append(str(e))
# ... (省略了其他代码)
return errors
exec() 函数在 Python 中用于执行动态生成的代码。当 validate_code 函数遍历抽象语法树 (AST) 并遇到一个函数定义 (ast.FunctionDef) 时,它会编译该函数定义并使用 exec(code_obj) 来执行它。由于 exec() 在当前进程的上下文中运行,并且没有对可执行的操作进行严格限制,这为攻击者提供了执行任意 Python 代码的机会。
3. 利用链 (Exploitation Chain)
- 攻击者构造恶意请求: 攻击者创建一个 HTTP POST 请求,目标是 Langflow 服务器的
/api/v1/validate/code端点。请求体中包含一个 JSON 对象,其中code字段的值是恶意 Python 代码。 - 服务器接收请求: Langflow 服务器接收到此请求。
- 代码传递给
validate_code:/api/v1/validate/code端点将code字段的内容(即恶意 Python 代码字符串)传递给validate_code函数。 - AST 解析:
validate_code函数使用ast.parse()将恶意代码字符串解析成抽象语法树 (AST)。 - 恶意装饰器执行: 当
validate_code函数遍历 AST 并遇到一个函数定义(即使是一个空的函数,如def foo(): pass)时,它会尝试执行该函数。如果该函数定义包含一个装饰器,Python 解释器会在函数体执行之前,先执行装饰器。攻击者利用这一点,在装饰器中嵌入恶意exec()语句。 - 远程代码执行 (RCE):
validate_code函数中的exec(code_obj)会执行包含恶意装饰器的函数定义。在执行装饰器时,攻击者注入的exec()语句被触发,从而在服务器上执行任意系统命令。 - 结果回显: 攻击者通过在恶意代码中引发异常,可以将命令的输出(例如
/etc/passwd的内容)作为 HTTP 响应中的错误信息返回给攻击者,实现命令执行结果的回显。
4. PoC 详细解释
以下是您提供的 PoC:
1
2
3
4
5
{
"code": "@exec('raise Exception(__import__("subprocess").check_output(["cat", "/etc/passwd"]))')
def foo():
pass"
}
让我们逐一解释其关键部分:
-
"code": "...":这是发送到/api/v1/validate/code端点的 JSON 请求体中的code字段,其值是待验证的 Python 代码字符串。 @exec('...')装饰器:- 在 Python 中,装饰器 (
@decorator) 是一种特殊类型的函数,它接受另一个函数作为参数,并返回一个新函数。装饰器在被装饰的函数定义时(而不是调用时)立即执行。 - 在这个 PoC 中,
@exec(...)并不是 Python 内置的exec函数,而是一个字符串字面量。当validate_code函数尝试编译并执行def foo(): pass这个函数定义时,它会先处理其装饰器。 - 由于
validate_code函数内部的exec(code_obj)会执行整个函数定义(包括装饰器),因此装饰器字符串'raise Exception(__import__("subprocess").check_output(["cat", "/etc/passwd"]))'会被作为 Python 代码执行。
- 在 Python 中,装饰器 (
raise Exception(...):- 攻击者希望获取其执行的系统命令的输出。通常,直接执行命令不会将输出返回给 HTTP 响应。
- 通过
raise Exception(...),攻击者强制 Python 解释器抛出一个异常。这个异常的参数就是攻击者想要回显的数据。 - 在
post_validate_code函数中,有一个try...except Exception as e:块会捕获这个异常,并将其str(e)作为 HTTP 500 响应的detail字段返回给客户端。这样,攻击者就能在响应中看到命令的输出。
__import__("subprocess"):__import__()是 Python 的一个内置函数,用于动态导入模块。它等同于import subprocess,但可以在运行时以字符串形式指定模块名。subprocess是 Python 的一个标准库模块,用于创建新的进程、连接到它们的输入/输出/错误管道,并获取它们的返回码。它允许 Python 程序执行外部系统命令。
.check_output(["cat", "/etc/passwd"]):- 这是
subprocess模块中的一个函数。 check_output()函数用于运行一个命令,并捕获它的标准输出。如果命令返回非零退出码,它会抛出一个CalledProcessError异常。["cat", "/etc/passwd"]是要执行的命令及其参数。cat /etc/passwd是一个常见的 Linux 命令,用于显示/etc/passwd文件的内容。这个文件包含了系统用户的基本信息,是攻击者在进行信息收集时常用的目标。
- 这是
综合起来,这个 PoC 的作用是:通过代码注入,在 Langflow 服务器上执行 cat /etc/passwd 命令,并将该命令的输出通过 HTTP 响应中的异常信息回显给攻击者。
5. 修复方案分析 (Langflow >= 1.3.0)
根据 GitHub PR #6911 的信息,Langflow 1.3.0 版本通过引入以下更改来修复此漏洞:
- 为
/api/v1/validate/code端点添加了用户认证。
这意味着,从 Langflow 1.3.0 版本开始,未经身份验证的用户将无法访问 /api/v1/validate/code 端点。由于 CVE-2025-3248 明确指出这是一个“未经身份验证的远程代码执行”漏洞,因此添加认证有效地阻止了未经身份验证的攻击者利用此漏洞。
5.1. 权限验证的实现方式
修复方案的核心在于利用 FastAPI 的依赖注入 (Dependency Injection) 系统来强制执行用户认证。
在 src/backend/base/langflow/api/v1/validate.py 文件中,post_validate_code 函数的签名被修改为包含一个 CurrentActiveUser 类型的参数:
1
2
3
4
5
6
# langflow-1.3.0/src/backend/base/langflow/api/v1/validate.py
from langflow.api.utils import CurrentActiveUser # <-- 关键导入
@router.post("/code", status_code=200)
async def post_validate_code(code: Code, _current_user: CurrentActiveUser) -> CodeValidationResponse: # <-- 关键参数
# ... 函数体 ...
详细解释:
CurrentActiveUser依赖:_current_user: CurrentActiveUser参数是一个 FastAPI 的依赖项 (Dependency)。在 Langflow 的代码库中,CurrentActiveUser被定义为一个可调用对象(通常是一个函数或一个类),它负责验证当前请求的用户是否已认证。- 这个依赖项通常会检查请求头中的认证令牌(例如
Authorization: Bearer <token>),并根据令牌的有效性来确定用户身份。
- FastAPI 依赖注入机制:
- 当 FastAPI 收到一个对
/api/v1/validate/code端点的请求时,它会尝试解析post_validate_code函数的所有参数。 - 对于
_current_user: CurrentActiveUser这个参数,FastAPI 会在执行post_validate_code函数的业务逻辑之前,先调用CurrentActiveUser依赖项。 - 如果
CurrentActiveUser依赖项成功验证了用户的身份(即用户已认证并提供了有效的令牌),它会返回一个用户对象(或类似的信息),并将其赋值给_current_user参数,然后post_validate_code函数的业务逻辑(调用validate_code)才会继续执行。 - 如果
CurrentActiveUser依赖项验证失败(例如,请求中没有认证令牌,或者令牌无效),它会立即抛出一个HTTPException(通常是HTTPException(status_code=401, detail="Not authenticated")或类似的错误)。 - 一旦依赖项抛出异常,FastAPI 会捕获这个异常,并将其转换为相应的 HTTP 错误响应(例如 401 Unauthorized),而
post_validate_code函数的实际业务逻辑(调用validate_code)将根本不会被执行。
- 当 FastAPI 收到一个对
总结:
Langflow 1.3.0 通过在 /api/v1/validate/code 端点的处理函数 post_validate_code 中引入 _current_user: CurrentActiveUser 依赖项,实现了权限验证。这个依赖项在函数执行之前强制检查用户是否已认证。如果用户未认证,请求会在到达 validate_code 函数之前就被拒绝,从而有效地阻止了未经身份验证的攻击者利用代码注入漏洞。
重要说明:
尽管添加了认证,但 src/backend/base/langflow/utils/validate.py 中的 validate_code 函数内部的 exec(code_obj) 调用本身并没有被移除或沙箱化。这意味着,如果一个已认证的用户被允许提交任意代码到此端点,理论上仍然存在代码执行的风险。然而,这已经超出了原始 CVE 描述的“未经身份验证”的范畴,并且通常需要额外的权限管理和代码审查来缓解。
6. 进一步审计:是否存在其他未经授权的调用点?
鉴于 validate_code 函数本身并未进行沙箱化处理,我们进行了进一步的审计,以确定在 Langflow 1.3.1 版本中是否存在其他未经身份验证的端点,可能仍然调用易受攻击的 langflow.utils.validate.validate_code 函数,从而导致 RCE 或代码注入。
6.1. validate_code 函数的调用点分析
我们对 langflow-1.3.1 代码库进行了全面搜索,查找所有 validate_code 的调用。主要发现如下:
src/backend/base/langflow/utils/validate.py: 这是validate_code函数的定义位置,其中包含exec()调用。src/backend/base/langflow/api/v1/validate.py: 此文件包含post_validate_code端点,该端点调用了langflow.utils.validate.validate_code。如前所述,此端点现在已通过_current_user: CurrentActiveUser依赖项强制执行用户认证。- 测试文件 (
src/backend/tests/...): 多个测试文件直接或间接调用validate_code进行功能测试。这些是开发和测试环境中的调用,不构成生产环境中的可利用路径。 src/backend/base/langflow/custom/directory_reader/directory_reader.py: 此文件定义了一个名为DirectoryReader.validate_code的方法。经过详细审查,该方法仅使用ast.parse(file_content)进行静态语法检查,不包含exec()调用,因此它不是原始 RCE 漏洞的绕过点或“漏网之鱼”。
6.2. 针对完全限定名的搜索
为了确保没有遗漏任何通过显式导入并调用易受攻击函数的场景,我们进一步搜索了 langflow.utils.validate.validate_code(即易受攻击函数的完全限定名)。
搜索结果显示,在 langflow-1.3.1 目录中没有找到任何匹配项。
6.3. 审计结论
基于以上分析,我们可以得出以下结论:
langflow-1.3.1版本中,易受攻击的langflow.utils.validate.validate_code函数唯一被生产代码导入和调用的地方是/api/v1/validate/code端点。- 该端点已通过 FastAPI 的依赖注入机制强制执行用户认证,从而有效地阻止了未经身份验证的攻击者访问并利用此漏洞。
DirectoryReader.validate_code方法是一个独立的、安全的语法检查函数,不会导致 RCE。- 没有发现其他未经授权即可触发
langflow.utils.validate.validate_code函数的“漏网之鱼”端点。
因此,Langflow 1.3.0 及更高版本中针对 CVE-2025-3248 的认证修复是全面的,有效地解决了未经身份验证的远程代码执行问题。
7. 结论和建议
CVE-2025-3248 是 Langflow 1.3.0 之前版本中一个严重的未经身份验证的远程代码执行漏洞。该漏洞源于服务器在处理用户提交的代码时,直接使用 exec() 函数执行了未经验证的输入。
建议:
强烈建议所有 Langflow 用户立即升级到 Langflow 1.3.0 或更高版本,以确保此漏洞得到修复。
8. 参考资料
- CVE 编号: CVE-2025-3248
- GitHub Pull Request: fix: auth current user on code validation #6911
- GitHub Release: Langflow 1.3.0