CVE-2025-68700
Authenticated RCE in RAGFlow via unsafe eval() on untrusted output.
- CVSS 8.6
- CWE-78
- Input Validation and Sanitization
- Remote
RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine. In versions prior to 0.23.0, a low-privileged authenticated user (normal login account) can execute arbitrary system commands on the server host process via the frontend Canvas CodeExec component, completely bypassing sandbox isolation. This occurs because untrusted data (stdout) is parsed using eval() with no filtering or sandboxing. The intended design was to "automatically convert string results into Python objects," but this effectively executes attacker-controlled code. Additional endpoints lack access control or contain inverted permission logic, significantly expanding the attack surface and enabling chained exploitation. Version 0.23.0 contains a patch for the issue.
- CWE
- CWE-78
- CVSS base score
- 8.6
- Published
- 2025-12-31
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Algorithm
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- RAGFlow
- Fixed by upgrading
- Yes
Solution
Upgrade to RAGFlow version 0.23.0.
Vulnerable code sample
import os
import sys
# In a real-world scenario, this would be part of a larger web application (e.g., Flask, Django)
# where an authenticated user submits code to be run in a "sandboxed" environment.
# The vulnerability lies not in the sandbox, but in how the output of the sandboxed
# process is handled by the main application.
def simulate_sandboxed_code_execution(user_provided_code):
"""
Simulates the execution of user code in a supposed sandbox.
In the actual RAGFlow application, this would be the Canvas CodeExec component.
This function's return value represents the standard output (stdout) of that execution.
"""
# The attacker doesn't need to break the sandbox. They just need to control
# the string that gets printed to stdout.
# A legitimate user's code might print a dictionary or list as a string, e.g., print("{'key': 'value'}").
# An attacker's code will print a string that is a valid, malicious Python expression.
# For this demonstration, we assume the user_provided_code successfully executes
# and we just return its intended stdout string.
# Example of a legitimate stdout string:
# return "{'status': 'success', 'items': [1, 2, 3]}"
# Example of a malicious stdout string crafted by an attacker:
# The user's code in the sandbox would be:
# print("__import__('os').system('echo VULNERABLE: Code execution successful')")
return "__import__('os').system('echo VULNERABLE: An arbitrary command was executed on the server by CVE-2025-68700.')"
def process_canvas_output(stdout_data: str):
"""
This function represents the vulnerable part of the RAGFlow backend.
It receives the stdout from the executed code and attempts to parse it.
CVE-2025-68700 Description: "untrusted data (stdout) is parsed using eval()
with no filtering or sandboxing. The intended design was to 'automatically
convert string results into Python objects,' but this effectively executes
attacker-controlled code."
"""
print(f"[SERVER LOG] Received stdout from component: '{stdout_data}'")
print("[SERVER LOG] Attempting to parse stdout into a Python object using eval()...")
try:
# THE VULNERABILITY IS HERE:
# eval() is used on untrusted input. It will execute any valid Python code,
# not just parse data structures like dictionaries or lists.
parsed_result = eval(stdout_data)
print(f"[SERVER LOG] Successfully parsed stdout. Result: {parsed_result}")
return parsed_result
except Exception as e:
print(f"[SERVER LOG] Error during eval: {e}", file=sys.stderr)
return None
if __name__ == '__main__':
# 1. An attacker, as a low-privileged authenticated user, uses the frontend
# Canvas CodeExec component to run a simple piece of code.
# The code is designed to print a malicious string to stdout.
attacker_code_for_sandbox = """
# This code runs in the "sandbox". Its only goal is to print a malicious string.
import os
command_to_execute = 'echo VULNERABLE: An arbitrary command was executed on the server by CVE-2025-68700.'
print(f"__import__('os').system('{command_to_execute}')")
"""
print("--- Simulating RAGFlow < 0.23.0 Vulnerability (CVE-2025-68700) ---")
print("Step 1: Attacker submits code that prints a malicious string to stdout.")
# 2. The "sandboxed" environment runs the code and captures the stdout.
# The stdout is now a string containing a command execution payload.
captured_stdout = simulate_sandboxed_code_execution(attacker_code_for_sandbox)
print("\nStep 2: The backend server receives the stdout from the sandboxed process.")
# 3. The main application backend receives this stdout string and, in its
# attempt to be helpful by converting stringified objects back into
# Python objects, it uses the dangerous `eval()` function.
print("\nStep 3: The vulnerable backend uses eval() to process the stdout, triggering the RCE.")
print("-" * 20)
# This call triggers the remote code execution.
process_canvas_output(captured_stdout)
print("-" * 20)
print("\n[DEMO] The 'echo' command above was executed by the Python process, demonstrating the vulnerability.")Patched code sample
import ast
import json
def safe_parse_code_output(stdout_string: str):
"""
Safely parses a string output from a code execution component.
This function replaces the dangerous use of `eval()` with a safer parsing
mechanism to prevent arbitrary code execution (CVE-2025-68700).
The intended functionality was to convert string representations of Python
objects (like lists or dictionaries) into actual objects. The vulnerability
arose because `eval()` executes any code, not just parses literals.
The fix involves two stages:
1. Try to parse the output as JSON, which is a common and safe data
interchange format.
2. If JSON parsing fails, fall back to `ast.literal_eval()`. This function
can safely evaluate strings containing Python literals (strings, numbers,
tuples, lists, dicts, booleans, and None) but will raise an exception
for any other code, such as function calls or imports, thus neutralizing
the vulnerability.
Args:
stdout_string: The string captured from standard output, potentially
controlled by a user.
Returns:
The parsed Python object if successful, otherwise the original string.
"""
if not isinstance(stdout_string, str):
return stdout_string
cleaned_string = stdout_string.strip()
# First, attempt to parse as JSON, a common and safe format.
try:
return json.loads(cleaned_string)
except json.JSONDecodeError:
# If it's not valid JSON, proceed to literal_eval.
pass
# Second, use ast.literal_eval as a safe alternative to eval().
# It only processes Python literals and prevents execution of arbitrary code.
try:
# For example, this will correctly parse "['a', 'b']" into a list.
# However, it will raise an error for "__import__('os').system('ls')",
# which is what prevents the code execution vulnerability.
return ast.literal_eval(cleaned_string)
except (ValueError, SyntaxError, MemoryError, TypeError):
# If the string is not a valid Python literal (e.g., it contains
# malicious code or is just plain text), return the original string.
return stdout_string
# # --- Demonstration of the fix ---
#
# # 1. Legitimate use case: converting a string representation of a list
# legitimate_output = "['result1', 'result2', {'key': 'value'}]"
# parsed_legitimate = safe_parse_code_output(legitimate_output)
# print(f"Original legitimate string: {legitimate_output}")
# print(f"Safely parsed object: {parsed_legitimate}")
# print(f"Type of parsed object: {type(parsed_legitimate)}\n")
#
# # 2. Malicious input that would have been executed by eval()
# malicious_code = "__import__('os').system('echo Vulnerability Exploited!')"
# parsed_malicious = safe_parse_code_output(malicious_code)
# print(f"Original malicious string: {malicious_code}")
# print(f"Result after safe parsing: {parsed_malicious}")
# print(f"Type of result: {type(parsed_malicious)}")
# print("--> The malicious code was not executed; the original string was returned safely.\n")
#
# # 3. Normal string output that is not a literal
# plain_text_output = "This is just a normal sentence."
# parsed_text = safe_parse_code_output(plain_text_output)
# print(f"Original plain text: {plain_text_output}")
# print(f"Result after safe parsing: {parsed_text}")
# print(f"Type of result: {type(parsed_text)}")
# print("--> The plain text was returned as-is, without errors.")Payload
__import__('os').system('id')
Cite this entry
@misc{vaitp:cve202568700,
title = {{Authenticated RCE in RAGFlow via unsafe eval() on untrusted output.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-68700},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-68700/}}
}
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 ::
