GitHub Security Advisory: GHSA-ghcv-22jf-vfxm
Package: wwbn/avideo (COMPOSER)
Severity: HIGH (CVSS 7.2)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: <= 29.0
Patched in: unpatched

Summary: AVideo has an Incomplete Fix for YPTSocket autoEvalCodeOnHTML Strip: Unauthenticated Cross-User JavaScript Execution via `$msg['json']` Relay Bypass

Details:
## Summary

The server-side mitigation for the YPTSocket `autoEvalCodeOnHTML` eval sink (prior advisory GHSA-gph2-j4c9-vhhr, commit `c08694bf6`) only strips the payload when it sits under `$json['msg']`, but the relay function `msgToResourceId()` selects the outbound message from `$msg['json']` *before* `$msg['msg']`. An unauthenticated attacker can obtain a WebSocket token from `plugin/YPTSocket/getWebSocket.json.php`, connect to the WebSocket server, and send a message with `autoEvalCodeOnHTML` nested under a top-level `json` field — the strip branch is skipped, the relay delivers the payload verbatim to any logged-in user identified by `to_users_id`, and the client script runs it through `eval()`.

## Details

### Entry point (unauthenticated)

`plugin/YPTSocket/getWebSocket.json.php` (lines 1–21) issues a valid WebSocket token to any caller, with no authentication or CSRF check:

```php
$obj->webSocketToken = getEncryptedInfo(0);
$obj->webSocketURL = YPTSocket::getWebSocketURL();
die(json_encode($obj));
```

`getEncryptedInfo()` defaults to `sentFrom = 'browser'` and a non-CLI flag (`plugin/YPTSocket/functions.php:3-47`), so a token minted for an anonymous browser client will cause the strip branch below to run — which is exactly what we want to audit.

### Incomplete strip (the fix from commit c08694bf6)

`plugin/YPTSocket/Message.php:236-247`:

```php
// Strip eval-able fields from browser/guest messages.
if (empty($msgObj->isCommandLineInterface) && ($msgObj->sentFrom ?? '') !== 'php') {
    if (is_array($json['msg'] ?? null)) {
        unset($json['msg']['autoEvalCodeOnHTML']);          // <-- only strips $json['msg']
    }
    if (isset($json['callback']) && !preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', (string)$json['callback'])) {
        unset($json['callback']);
    }
}
```

If the incoming `$json['msg']` is a scalar (e.g. the string `"x"`), `is_array(...)` is false and the strip is skipped entirely. Any eval-able content that lives elsewhere in `$json` passes through untouched. The same flawed check exists in `plugin/YPTSocket/MessageSQLiteV2.php:285-293`.

### Relay preference picks the untouched field

`plugin/YPTSocket/Message.php:316-322` (and the mirror at `MessageSQLiteV2.php:396-402`):

```php
if (!empty($msg['json'])) {
    $obj['msg'] = $msg['json'];          // <-- preferred carrier; never stripped
} else if (!empty($msg['msg'])) {
    $obj['msg'] = $msg['msg'];
} else {
    $obj['msg'] = $msg;
}
```

An attacker payload shaped as `{"msg": "x", "json": {"autoEvalCodeOnHTML": "<js>"}, "to_users_id": <victim>}` therefore:

1. Passes `switch ($json->msg)` into the `default` case (Message.php:211, 228).
2. `msgToArray($json)` converts to array. The strip branch enters because `sentFrom === 'browser'`, but `is_array("x")` is false and the strip is skipped.
3. Routing lands on `msgToUsers_id($json, $json['to_users_id'])` (Message.php:253), which for each matching resource calls `msgToResourceId($msg, $resourceId)` (Message.php:379).