Skip to content

Commit db781cd

Browse files
1 parent 5faecb2 commit db781cd

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-6h7h-m7p5-hjqp",
4+
"modified": "2026-03-30T18:04:10Z",
5+
"published": "2026-03-30T18:04:10Z",
6+
"aliases": [
7+
"CVE-2026-34372"
8+
],
9+
"summary": "Sulu checks fix permissions for subentities endpoints",
10+
"details": "### Impact\n\nA user which has permission for the Sulu Admin via atleast one role could have access to the subentities of contacts via the admin API without even have permission for contacts.\n\n### Patches\n\nThe issue was patched in release 2.6.22 and 3.0.5.\n\n### Workarounds\n\nCreate a Symfony Request Listener checking the permissions for the specific roles.\n\n### Resources\n\nGithub Advisory: https://github.com/sulu/sulu/security/advisories/GHSA-6h7h-m7p5-hjqp",
11+
"severity": [],
12+
"affected": [
13+
{
14+
"package": {
15+
"ecosystem": "Packagist",
16+
"name": "sulu/sulu"
17+
},
18+
"ranges": [
19+
{
20+
"type": "ECOSYSTEM",
21+
"events": [
22+
{
23+
"introduced": "1.0.0"
24+
},
25+
{
26+
"fixed": "2.6.22"
27+
}
28+
]
29+
}
30+
]
31+
},
32+
{
33+
"package": {
34+
"ecosystem": "Packagist",
35+
"name": "sulu/sulu"
36+
},
37+
"ranges": [
38+
{
39+
"type": "ECOSYSTEM",
40+
"events": [
41+
{
42+
"introduced": "3.0.0"
43+
},
44+
{
45+
"fixed": "3.0.5"
46+
}
47+
]
48+
}
49+
]
50+
}
51+
],
52+
"references": [
53+
{
54+
"type": "WEB",
55+
"url": "https://github.com/sulu/sulu/security/advisories/GHSA-6h7h-m7p5-hjqp"
56+
},
57+
{
58+
"type": "PACKAGE",
59+
"url": "https://github.com/sulu/sulu"
60+
},
61+
{
62+
"type": "WEB",
63+
"url": "https://github.com/sulu/sulu/releases/tag/2.6.22"
64+
},
65+
{
66+
"type": "WEB",
67+
"url": "https://github.com/sulu/sulu/releases/tag/3.0.5"
68+
}
69+
],
70+
"database_specific": {
71+
"cwe_ids": [
72+
"CWE-288"
73+
],
74+
"severity": "MODERATE",
75+
"github_reviewed": true,
76+
"github_reviewed_at": "2026-03-30T18:04:10Z",
77+
"nvd_published_at": null
78+
}
79+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-q6jj-r49p-94fh",
4+
"modified": "2026-03-30T18:03:26Z",
5+
"published": "2026-03-30T18:03:26Z",
6+
"aliases": [
7+
"CVE-2026-34369"
8+
],
9+
"summary": "AVideo has Video Password Protection Bypass via API Endpoints Returning Full Playback Sources Without Password Verification",
10+
"details": "## Summary\n\nThe `get_api_video_file` and `get_api_video` API endpoints in AVideo return full video playback sources (direct MP4 URLs, HLS manifests) for password-protected videos without verifying the video password. While the normal web playback flow enforces password checks via the `CustomizeUser::getModeYouTube()` hook, this enforcement is completely absent from the API code path. An unauthenticated attacker can retrieve direct playback URLs for any password-protected video by calling the API directly.\n\n## Details\n\nThe video password protection is enforced in the web UI via `CustomizeUser::getModeYouTube()` (`plugin/CustomizeUser/CustomizeUser.php:787`), which calls `videoPasswordIsGood()` before rendering the video player. However, this hook is only invoked during web page rendering — the API endpoints bypass it entirely.\n\n**Vulnerable endpoint 1 — `get_api_video_file` (`plugin/API/API.php:986-1004`):**\n\n```php\npublic function get_api_video_file($parameters)\n{\n global $global;\n $obj = $this->startResponseObject($parameters);\n $obj->videos_id = $parameters['videos_id'];\n if (!self::isAPISecretValid()) {\n if (!User::canWatchVideoWithAds($obj->videos_id)) {\n return new ApiObject(\"You cannot watch this video\");\n }\n }\n $video = new Video('', '', $obj->videos_id);\n $obj->filename = $video->getFilename();\n // ...\n $obj->video_file = Video::getHigherVideoPathFromID($obj->videos_id);\n $obj->sources = getSources($obj->filename, true);\n return new ApiObject(\"\", false, $obj);\n}\n```\n\nThe only access check is `User::canWatchVideoWithAds()` (`objects/user.php:1102-1159`), which checks admin status, video active status, owner status, and plugin-level restrictions (subscription/PPV). It does **not** check `video_password`. Password-protected videos have status `'a'` (active), which passes all checks.\n\n**Vulnerable endpoint 2 — `get_api_video` (`plugin/API/API.php:1635-1810`):**\n\nThis endpoint returns video metadata including full `videos` paths (line 1759) and `sources` arrays (line 1785) for all videos in query results, with no password verification anywhere in the function.\n\n**The intended password check exists but is never called from these endpoints:**\n\n`Video::verifyVideoPassword()` (`objects/video.php:543-553`) is the proper password verification function, and `get_api_video_password_is_correct` exists as a separate API endpoint — proving password verification was intended as an access control. But neither `get_api_video_file` nor `get_api_video` invoke any password check.\n\n## PoC\n\n```bash\n# Step 1: Identify a password-protected video via the video list API\ncurl -s 'https://target.com/plugin/API/get.json.php?APIName=video&rowCount=50' | \\\n python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nfor v in data.get('response',{}).get('rows',[]):\n if v.get('video_password'):\n print(f'ID: {v[\\\"id\\\"]}, Title: {v[\\\"title\\\"]}, Password Protected: YES')\n print(f' Direct sources: {json.dumps(v.get(\\\"sources\\\",[])[0] if v.get(\\\"sources\\\") else \\\"none\\\")}')\"\n\n# Step 2: Retrieve full playback sources for the password-protected video\ncurl -s 'https://target.com/plugin/API/get.json.php?APIName=video_file&videos_id=<PROTECTED_VIDEO_ID>'\n\n# Expected: access denied or password prompt\n# Actual: full response with direct MP4/HLS URLs:\n# {\"error\":false,\"response\":{\"videos_id\":\"123\",\"filename\":\"video_abc\",\n# \"video_file\":\"https://target.com/videos/video_abc/video_abc_HD.mp4\",\n# \"sources\":[{\"src\":\"https://target.com/videos/video_abc/video_abc_HD.mp4\",\"type\":\"video/mp4\"}]}}\n\n# Step 3: Download the protected video directly\ncurl -O 'https://target.com/videos/video_abc/video_abc_HD.mp4'\n```\n\n## Impact\n\nAny unauthenticated user can retrieve direct playable video URLs for all password-protected videos, completely bypassing the password requirement. The `get_api_video` endpoint additionally exposes which videos are password-protected (via the `video_password` field set to `'1'`), allowing targeted enumeration. This renders the `video_password` feature ineffective for any content accessible through the API, which includes mobile apps, third-party integrations, and direct API consumers.\n\n## Recommended Fix\n\nAdd password verification to both API endpoints before returning video sources. In `plugin/API/API.php`:\n\n```php\npublic function get_api_video_file($parameters)\n{\n global $global;\n $obj = $this->startResponseObject($parameters);\n $obj->videos_id = $parameters['videos_id'];\n if (!self::isAPISecretValid()) {\n if (!User::canWatchVideoWithAds($obj->videos_id)) {\n return new ApiObject(\"You cannot watch this video\");\n }\n // Check video password protection\n $video = new Video('', '', $obj->videos_id);\n $storedPassword = $video->getVideo_password();\n if (!empty($storedPassword)) {\n $providedPassword = @$parameters['video_password'];\n if (empty($providedPassword) || !Video::verifyVideoPassword($providedPassword, $storedPassword)) {\n return new ApiObject(\"Video password required\", true);\n }\n }\n }\n // ... rest of function\n}\n```\n\nApply the same check in `get_api_video()` before populating the `videos` and `sources` fields (around line 1759), replacing source data with an empty object when the password is not provided or incorrect. Also fix `get_api_video_password_is_correct` to use `Video::verifyVideoPassword()` instead of direct `==` comparison (line 1126), which currently fails for bcrypt hashes.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Packagist",
21+
"name": "wwbn/avideo"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "26.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-q6jj-r49p-94fh"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34369"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/WWBN/AVideo/commit/be344206f2f461c034ad2f1c5d8212dd8a52b8c7"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/WWBN/AVideo"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-862"
59+
],
60+
"severity": "MODERATE",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2026-03-30T18:03:26Z",
63+
"nvd_published_at": "2026-03-27T19:16:42Z"
64+
}
65+
}

0 commit comments

Comments
 (0)