Breaking the AWS MCP Python Sandbox

AWS MCP Server is a managed remote Model Context Protocol (MCP) server that lets AI agents call AWS tools. The aws___run_script tool accepts a short Python script and runs it in an AWS Lambda sandbox. The script can combine multiple AWS API calls, filter responses, and process the result in a single request.
The sandbox restricts imports, built-in functions, attribute access, and file operations. However, these restrictions can be bypassed to achieve system command execution inside the Lambda environment.
The submitted code passed through two layers of protection:
  1. Static AST validator — inspected the script's Abstract Syntax Tree (AST) and rejected suspicious operations before execution, such as reading dunder attributes (obj.__class__), calling getattr(), or importing operator to use operator.attrgetter().
  2. Runtime layer — replaced built-ins with restricted implementations or proxy objects. For example, type() was replaced with _safe_type(), which returned restricted proxy classes that blocked access to sensitive attributes. open() was also restricted, while getattr() was blocked.
Exploitation chain:
  1. Bypassing the string restriction. The validator rejected dunder names written as plain strings, so the name was assembled at runtime using chr(): chr(95)2 + "class" + chr(95)2.
  2. Obtaining a class. Defining a class normally was not possible in the sandbox, but one could be created through collections.namedtuple(), which internally used type(...) and allowed __match_args__ to be reassigned.
  3. Recovering a real function. __match_args__ was set to __reduce_ex__. Calling this method with pickle protocol 4 returned a recipe for reconstructing the object. Its first element was copyreg.__newobj__ — a real Python function, not a proxy.
  4. Accessing global names. A function object was needed for isinstance() to verify that the object was a function. The proxy returned by _safe_type() for functions did not inherit from the immutable function type; instead, it was built from scratch and was therefore mutable. Its __match_args__ was set to ("__globals__",), allowing a class pattern to extract __globals__ from the real function.
  5. Breaking into imports. __globals__ contained the original __builtins__ dictionary, including the real __import__ function. This was used to load os and subprocess.
Results: • System command execution inside the Lambda container as the Lambda user: uid=993(sbx_user1051) • Access to environment variables containing the Lambda execution role credentials: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN • Filesystem read access, including access to the sandbox's own source code
Limitations The recovered credentials belonged to an IAM role in an AWS-owned account, not a customer account. They were explicitly denied for AWS API calls other than sts:GetCallerIdentity, which is always available. No access to other sessions or customer data was found — the escape was confined to the Lambda container created for the session.
Vendors
Products
More