GitHub Security Advisory: GHSA-gph2-j4c9-vhhr
Package: wwbn/avideo (COMPOSER)
Severity: CRITICAL (CVSS 10.0)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: <= 29.0
Patched in: unpatched

Summary: WWBN AVideo YPTSocket WebSocket Broadcast Relay Leads to Unauthenticated Cross-User JavaScript Execution via Client-Side eval() Sinks

Details:
## Summary

The YPTSocket plugin's WebSocket server relays attacker-supplied JSON message bodies to every connected client without sanitizing the `msg` or `callback` fields. On the client side, `plugin/YPTSocket/script.js` contains two `eval()` sinks fed directly by those relayed fields (`json.msg.autoEvalCodeOnHTML` at line 568 and `json.callback` at line 95). Because tokens are minted for anonymous visitors and never revalidated beyond decryption, an unauthenticated attacker can broadcast arbitrary JavaScript that executes in the origin of every currently-connected user (including administrators), resulting in universal account takeover, session theft, and privileged action execution.

## Details

### Token issuance is unauthenticated

`plugin/YPTSocket/getWebSocket.json.php:11-21` returns a token to anyone whose request reaches the endpoint — the only check is that the plugin is enabled:

```php
if(!AVideoPlugin::isEnabledByName("YPTSocket")){
    $obj->msg = "Socket plugin not enabled";
    die(json_encode($obj));
}
$obj->error = false;
$obj->webSocketToken = getEncryptedInfo(0);
$obj->webSocketURL = YPTSocket::getWebSocketURL();
```

`getEncryptedInfo()` in `plugin/YPTSocket/functions.php:3-16` populates `from_users_id = User::getId()` (0 for guests) and `isAdmin = User::isAdmin()` (false for guests). The issued token is accepted by the WebSocket server's `onOpen` handler (`Message.php:44-52`) solely by successful decryption — there is no requirement for the connecting principal to be authenticated.

### Server relays attacker JSON verbatim

`plugin/YPTSocket/Message.php:191-245` — the default branch of `onMessage` only rewrites `from_identification`:

```php
public function onMessage(ConnectionInterface $from, $msg) {
    ...
    $json = _json_decode($msg);
    if (empty($json->webSocketToken)) { return false; }
    if (!$msgObj = getDecryptedInfo($json->webSocketToken)) { return false; }

    switch ($json->msg) {
        ...
        default:
            $this->msgToArray($json);
            if (isset($json['from_identification'])) {
                $json['from_identification'] = strip_tags((string)($msgObj->user_name ?? ''));
            }
            ...
            } else {
                $this->msgToAll($from, $json);  // broadcast
            }
            break;
    }
}
```

`msgToResourceId()` at `Message.php:297-310` copies the attacker-controlled `callback` and `msg` fields into the outbound payload:

```php
if (isset($msg['callback'])) {
    $obj['callback'] = $msg['callback'];  // tainted
    ...
}
...
} else if (!empty($msg['msg'])) {
    $obj['msg'] = $msg['msg'];  // tainted — entire object forwarded verbatim
}
```

`$obj` is JSON-encoded at line 335 and sent to every connected client.

### Client-side sink #1: `autoEvalCodeOnHTML` → eval

`plugin/YPTSocket/script.js:163-169` (raw WebSocket transport) sets every inbound frame as `yptSocketResponse` and unconditionally calls `parseSocketResponse()`:

```js
connWS.onmessage =