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.
- CWE
- CWE-20
- 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 ::
