GitHub Security Advisory: GHSA-x2pw-9c38-cp2j
Package: wwbn/avideo (COMPOSER)
Severity: MODERATE (CVSS 5.4)
CWE: CWE-352: Cross-Site Request Forgery (CSRF)
Vulnerable versions: <= 29.0
Patched in: unpatched

Summary: WWBN AVideo: Missing CSRF Protection on State-Changing JSON Endpoints Enables Forced Comment Creation, Vote Manipulation, and Category Asset Deletion

Details:
## Summary

Multiple AVideo JSON endpoints under `objects/` accept state-changing requests via `$_REQUEST`/`$_GET` and persist changes tied to the caller's session user, without any anti-CSRF token, origin check, or referer check. A malicious page visited by a logged-in victim can silently:

1. Cast/flip the victim's like/dislike on any comment (`objects/comments_like.json.php`).
2. Post a comment authored by the victim on any video, with attacker-chosen text (`objects/commentAddNew.json.php`).
3. Delete assets from any category (`objects/categoryDeleteAssets.json.php`) when the victim has category management rights.

Each endpoint is reachable from a browser via a simple `<img src="…">` tag or form submission, so exploitation only requires the victim to load an attacker-controlled HTML resource.

## Details

AVideo exposes a helper, `forbidIfIsUntrustedRequest()` (`objects/functionsSecurity.php:138`), that rejects cross-origin requests when the `Referer`/`Origin` does not match `webSiteRootURL`. It is only invoked in one file in the tree — `objects/userUpdate.json.php:18` — and is *not* applied to the endpoints below. There is also an `isGlobalTokenValid()` helper (`objects/functions.php:2313`) intended for CSRF-style token checks; none of the affected endpoints call it. `allowOrigin()` only sets CORS response headers and does not prevent cookie-bearing top-level or image requests from reaching the server.

### 1. `objects/comments_like.json.php` — CSRF → forced like/dislike

```php
// objects/comments_like.json.php
15: if (empty($_POST['comments_id']) && !empty($_GET['comments_id'])) {
16:     $_POST['comments_id'] = $_GET['comments_id'];
17: }
18:
19: $like = new CommentsLike($_GET['like'], $_POST['comments_id']);
20: echo json_encode(CommentsLike::getLikes($_POST['comments_id']));
```

The endpoint deliberately promotes `$_GET['comments_id']` to `$_POST['comments_id']` so the call works for either verb. `CommentsLike::__construct` (`objects/comments_like.php:18`) reads `User::getId()`, calls `load()` to fetch any prior vote, then `setLike()` + `save()` — issuing an `INSERT`/`UPDATE` on `comments_likes` keyed to the session user (`objects/comments_like.php:70-89`). There is no token check and no origin check.

### 2. `objects/commentAddNew.json.php` — CSRF → forced comment posting

```php
// objects/commentAddNew.json.php
34: if (!User::canComment()) {
35:     $obj->msg = __("Permission denied");
36:     die(json_encode($obj));
37: }
...
117: $objC = new Comment($_REQUEST['comment'], $_REQUEST['video']);
118: $objC->setComments_id_pai($_REQUEST['comments_id']);
...
124: $obj->comments_id = $objC->save();
```

All inputs come from `$_REQUEST`, so GET is fully supported. The only gate is `User::canComment()`, which is true for ordinary logged-in users. `isCommentASpam()` (lines 39–97) is a per-session rate limiter, not a CSRF defense — it accounts the victim's own session bucket, so it does not block a single forged write. The comment is persist