GitHub Security Advisory: GHSA-4mph-v827-f877
Package: locutus (NPM)
Severity: MODERATE (CVSS 0.0)
CWE: CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
Vulnerable versions: < 3.0.25
Patched in: 3.0.25

Summary: Locutus has Prototype Pollution via __proto__ Key Injection in unserialize()

Details:
## Summary

The `unserialize()` function in `locutus/php/var/unserialize` assigns deserialized keys to plain objects via bracket notation without filtering the `__proto__` key. When a PHP serialized payload contains `__proto__` as an array or object key, JavaScript's `__proto__` setter is invoked, replacing the deserialized object's prototype with attacker-controlled content. This enables property injection, for...in propagation of injected properties, and denial of service via built-in method override.

This is distinct from the previously reported prototype pollution in `parse_str` (GHSA-f98m-q3hr-p5wq, GHSA-rxrv-835q-v5mh) — `unserialize` is a different function with no mitigation applied.

## Details

The vulnerable code is in two functions within `src/php/var/unserialize.ts`:

**`expectArrayItems()` at line 358:**
```typescript
// src/php/var/unserialize.ts:329-366
function expectArrayItems(
  str: string,
  expectedItems = 0,
  cache: CacheFn,
): [UnserializedObject | UnserializedValue[], number] {
  // ...
  const items: UnserializedObject = {}
  // ...
  for (let i = 0; i < expectedItems; i++) {
    key = expectKeyOrIndex(str)
    // ...
    item = expectType(str, cache)
    // ...
    items[String(key[0])] = item[0]  // line 358 — no __proto__ filtering
  }
  // ...
}
```

**`expectObject()` at line 278:**
```typescript
// src/php/var/unserialize.ts:246-287
function expectObject(str: string, cache: CacheFn): ParsedResult {
  // ...
  const obj: UnserializedObject = {}
  // ...
  for (let i = 0; i < propCount; i++) {
    // ...
    obj[String(prop[0])] = value[0]  // line 278 — no __proto__ filtering
  }
  // ...
}
```

Both functions create a plain object (`{}`) and assign user-controlled keys via bracket notation. When the key is `__proto__`, JavaScript's `__proto__` setter replaces the object's prototype rather than creating a regular property. This means:

1. Properties in the attacker-supplied prototype become accessible via dot notation and the `in` operator
2. These properties are invisible to `Object.keys()`, `JSON.stringify()`, and `hasOwnProperty()`
3. They propagate to copies made via `for...in` loops, becoming real own properties
4. The attacker can override `hasOwnProperty`, `toString`, `valueOf` with non-function values

Notably, `parse_str` in the same package has a regex guard against `__proto__` (line 74 of `src/php/strings/parse_str.ts`), but no equivalent protection was applied to `unserialize`.

This is **not** global `Object.prototype` pollution — only the deserialized object's prototype is replaced. Other objects in the application are not affected.

## PoC

**Setup:**
```bash
npm install locutus@3.0.24
```

**Step 1 — Property injection via array deserialization:**
```js
import { unserialize } from 'locutus/php/var/unserialize';

const payload = 'a:2:{s:9:"__proto__";a:1:{s:7:"isAdmin";b:1;}s:4:"name";s:3:"bob";}';
const config = unserialize(payload);

console.log(config.isAdmin);           // true (injected via pro