VAITP Dataset

← Back to the dataset

CVE-2026-28797

RAGFlow authenticated SSTI in Agent components allows OS command execution.

  • CVSS 8.7
  • CWE-20
  • Input Validation and Sanitization
  • Remote

RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine. In versions 0.24.0 and prior, a Server-Side Template Injection (SSTI) vulnerability exists in RAGFlow's Agent workflow Text Processing (StringTransform) and Message components. These components use Python's jinja2.Template (unsandboxed) to render user-supplied templates, allowing any authenticated user to execute arbitrary operating system commands on the server. At time of publication, there are no publicly available patches.

CVSS base score
8.7
Published
2026-04-03
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Command Injection
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
RAGFlow

Solution

As of the time of publication, no patch is available. Users should disable the Agent workflow components or discontinue use until a patch is released.

Vulnerable code sample

import os
from flask import Flask, request, jsonify
from jinja2 import Template

# This represents a simplified RAGFlow application server.
# In a real RAGFlow instance, this logic would be part of a larger
# agent workflow processing system.

app = Flask(__name__)

# This simulates the "Text Processing (StringTransform)" or "Message" component
# mentioned in the CVE description.
@app.route('/api/agent/process', methods=['POST'])
def process_text_node():
    """
    An endpoint that takes a user-supplied Jinja2 template and some context
    data, then renders the template. This simulates the vulnerable behavior.
    """
    try:
        data = request.get_json()
        if not data or 'template' not in data:
            return jsonify({"error": "Missing 'template' in request body"}), 400

        user_template_string = data.get('template')

        # Sample context data that a user might have access to in their workflow.
        context_data = {
            "query": "What is the capital of France?",
            "document": "Paris is the capital and most populous city of France."
        }

        # VULNERABILITY: The user-supplied string `user_template_string` is
        # directly passed to `jinja2.Template` without any sandboxing.
        # This allows an attacker to inject template code that can access
        # Python's global scope and execute arbitrary commands.
        template = Template(user_template_string)
        
        # The template is rendered, executing any injected code.
        rendered_output = template.render(context_data)

        return jsonify({"result": rendered_output})

    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    # To exploit this, an authenticated user would send a POST request to /api/agent/process
    # with a malicious JSON payload like:
    # {
    #   "template": "{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}"
    # }
    # The server would execute the 'id' command and return its output.
    app.run(host='0.0.0.0', port=8080)

Patched code sample

import jinja2
from jinja2.sandbox import SandboxedEnvironment

# The CVE is fictional (year 2026), but it describes a real-world SSTI vulnerability
# pattern. The following code represents the fix for such a vulnerability.
# The vulnerability exists because user-provided template strings are rendered in an
# unsandboxed Jinja2 environment, allowing access to the underlying Python environment.
#
# The fix is to use jinja2.sandbox.SandboxedEnvironment, which restricts the template's
# access to potentially unsafe attributes and functions.

def fixed_template_render(user_supplied_template: str, context: dict) -> str:
    """
    Renders a user-supplied template string using a sandboxed Jinja2 environment
    to prevent Server-Side Template Injection (SSTI).

    Args:
        user_supplied_template: The template string provided by a user.
        context: A dictionary of variables to be made available in the template.

    Returns:
        The rendered string, or an error message if the template attempts
        to perform an unsafe operation.
    """
    # 1. Create a SandboxedEnvironment. This is the crucial step in the fix.
    #    This environment prevents templates from accessing unsafe attributes
    #    like .__class__, .__globals__, etc.
    sandboxed_env = SandboxedEnvironment()

    try:
        # 2. Load the user-provided template string into the sandboxed environment.
        template = sandboxed_env.from_string(user_supplied_template)

        # 3. Render the template with the provided context.
        rendered_output = template.render(context)
        return rendered_output

    except (jinja2.exceptions.SecurityError, TypeError, Exception) as e:
        # 4. If the template tries to access a forbidden attribute or function,
        #    the SandboxedEnvironment will raise a SecurityError or other exception.
        #    We catch this to prevent the application from crashing and can return
        #    a safe error message.
        error_message = f"Blocked unsafe template operation: {e}"
        # In a real application, this should be logged for security monitoring.
        print(error_message)
        return "Error: The provided template contains disallowed operations."


# --- Demonstration ---
if __name__ == '__main__':
    # A normal, safe template provided by a user
    safe_template = "Hello, {{ user_name }}! Welcome."

    # A malicious template attempting to execute OS commands (SSTI payload)
    # This payload tries to import the 'os' module and run the 'id' command.
    malicious_template = "Hello, {{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}"

    # The context data to be rendered in the template
    render_context = {"user_name": "Alex"}

    print("--- Testing the FIXED Rendering Function ---")

    # 1. Test with the safe template
    print(f"\nRendering safe template: '{safe_template}'")
    safe_result = fixed_template_render(safe_template, render_context)
    print(f"Result: {safe_result}")
    assert "Hello, Alex! Welcome." in safe_result

    print("-" * 40)

    # 2. Test with the malicious template
    print(f"\nRendering malicious template: '{malicious_template}'")
    malicious_result = fixed_template_render(malicious_template, render_context)
    print(f"Result: {malicious_result}")
    assert "disallowed" in malicious_result

    print("\nDemonstration complete. The malicious payload was successfully blocked by the sandbox.")

Payload

2
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

Cite this entry

@misc{vaitp:cve202628797,
  title        = {{RAGFlow authenticated SSTI in Agent components allows OS command execution.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-28797},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-28797/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::