Post

CVE-2025-3248 Langflow 未经身份验证的远程代码执行漏洞分析报告

Langflow 1.3.0 之前的版本因其代码验证接口 (/api/v1/validate/code) 对用户输入不当使用 exec() 函数,存在一个允许未经身份验证的攻击者执行任意代码的严重漏洞 (CVE-2025-3248),该问题已在新版中通过添加身份验证的方式修复。

CVE-2025-3248 Langflow 未经身份验证的远程代码执行漏洞分析报告

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)

  1. 攻击者构造恶意请求: 攻击者创建一个 HTTP POST 请求,目标是 Langflow 服务器的 /api/v1/validate/code 端点。请求体中包含一个 JSON 对象,其中 code 字段的值是恶意 Python 代码。
  2. 服务器接收请求: Langflow 服务器接收到此请求。
  3. 代码传递给 validate_code /api/v1/validate/code 端点将 code 字段的内容(即恶意 Python 代码字符串)传递给 validate_code 函数。
  4. AST 解析: validate_code 函数使用 ast.parse() 将恶意代码字符串解析成抽象语法树 (AST)。
  5. 恶意装饰器执行:validate_code 函数遍历 AST 并遇到一个函数定义(即使是一个空的函数,如 def foo(): pass)时,它会尝试执行该函数。如果该函数定义包含一个装饰器,Python 解释器会在函数体执行之前,先执行装饰器。攻击者利用这一点,在装饰器中嵌入恶意 exec() 语句。
  6. 远程代码执行 (RCE): validate_code 函数中的 exec(code_obj) 会执行包含恶意装饰器的函数定义。在执行装饰器时,攻击者注入的 exec() 语句被触发,从而在服务器上执行任意系统命令。
  7. 结果回显: 攻击者通过在恶意代码中引发异常,可以将命令的输出(例如 /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 代码执行。
  • 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: # <-- 关键参数
    # ... 函数体 ...

详细解释:

  1. CurrentActiveUser 依赖:
    • _current_user: CurrentActiveUser 参数是一个 FastAPI 的依赖项 (Dependency)。在 Langflow 的代码库中,CurrentActiveUser 被定义为一个可调用对象(通常是一个函数或一个类),它负责验证当前请求的用户是否已认证。
    • 这个依赖项通常会检查请求头中的认证令牌(例如 Authorization: Bearer <token>),并根据令牌的有效性来确定用户身份。
  2. 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)将根本不会被执行

总结:

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 的调用。主要发现如下:

  1. src/backend/base/langflow/utils/validate.py: 这是 validate_code 函数的定义位置,其中包含 exec() 调用。
  2. src/backend/base/langflow/api/v1/validate.py: 此文件包含 post_validate_code 端点,该端点调用了 langflow.utils.validate.validate_code。如前所述,此端点现在已通过 _current_user: CurrentActiveUser 依赖项强制执行用户认证。
  3. 测试文件 (src/backend/tests/...): 多个测试文件直接或间接调用 validate_code 进行功能测试。这些是开发和测试环境中的调用,不构成生产环境中的可利用路径。
  4. 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. 参考资料

This post is licensed under CC BY 4.0 by the author.