Skip to content

AVideo notify file-write, socket-callback, and rate-limit boundary batch

Sources: GitHub Security Advisories published on 2026-09-05 (15:30 UTC wave).

This batch is durable because it concentrates five distinct trust-boundary families on a single widely self-hosted media platform: a notify/IPC endpoint where decryption is treated as authentication, a caller-chosen path on the same endpoint, a hash-parameter lookup that returns complete user records unauthenticated, a websocket callback-name-to-global-function dispatch, and a bot User-Agent that disables rate limiting across all protected operations. Each is a reusable check for any platform with notify webhooks, view-stat endpoints, real-time socket layers, and API rate limiting.

Advisories covered

  • Unauthenticated arbitrary file write via notify.ffmpeg.json.php — GHSA-w6w9-fw29-2x88 (critical 9.8, CWE-73): the ffmpeg notify endpoint accepts a caller-chosen avideoRelativePath and writes the decrypted notifyCode payload to it. The notifyCode ciphertext is decrypted but never validated — any previously issued code (stream key, notify token, or sibling ciphertext family) replays as valid auth. Result: unauthenticated file write to the application root and subdirectories.
  • videoViewsInfo broken access control — GHSA-hr26-p4g3-798m (critical 9.1, CWE-200): when a hash parameter is supplied, the views-info endpoints return complete user records — password hashes, recovery tokens, and live session identifiers — to unauthenticated callers. A disclosed live session identifier is directly usable for viewer session hijack, including admin sessions.
  • Rate-limit bypass via bot User-Agent — GHSA-54jr-hcr3-c8jp (medium 6.5, CWE-307): sending a bot/crawler User-Agent header disables rate limiting on all eight protected operations, including the login brute-force guard. A single source IP can then brute-force any account without limit.
  • Unauthenticated XSS via YPTSocket callback dispatch — GHSA-jjf2-9w5j-xm7r (medium 7.2, CWE-79): with the YPTSocket plugin enabled, socket messages carry a callback name that is resolved to a global JS function on the connected client; crafted messages target globals like avideoConfirmHTML whose signatures assign untrusted data into innerHTML, executing script in other users' browsers with no authentication or interaction.
  • Weak external-login password generation — GHSA-hfr9-8xm8-cq6w (high 5.9, CWE-330): external-login account passwords are generated with rand() (31-bit integer space) and stored under unsalted MD5; with any access to a hash dump, plaintext recovery is minutes of offline brute-force.

Operator triage

  1. Treat exposed AVideo instances as critical priority when the ffmpeg/notify ingest path, video view-statistics endpoints, or the YPTSocket plugin are enabled. These five items are all reachable without credentials on default deployments that expose the media platform.
  2. notify.ffmpeg.json.php is the headline: enumerate every notify* / *.json.php IPC callback the platform registers (RTMP on_publish/on_stat, ffmpeg hooks, encoder callbacks) and test each for (a) auth semantics — is a token merely decrypted or actually validated (signature, expiry, one-use, issuer)? — and (b) path handling — is the write destination fixed or caller-shaped?
  3. For view-stat endpoints, test lookup keys (hash, id, key query params) with synthetic values and record the shape of the response: a positive is the response containing more fields than the requesting principal should see (hashes, recovery tokens, session IDs) rather than an empty/partial record.
  4. Rate limiting is a control only if it survives header shaping. Re-run every authenticated state-changing endpoint (login, registration, OTP, reset, API keys) with crawler UAs (Googlebot, Bingbot, slurp, YandexBot, plus the platform's own bot list if documented) and record whether the 429/lockout still fires.
  5. Socket/websocket layers: inventory the callback/event-name dispatch table on the JS side. Every event name that maps to a global function is a candidate for unauthenticated remote invocation; verify which globals sink untrusted arguments into innerHTML/document.write/eval/Function.
  6. Weak-primitive password generation: any rand()-seeded, unsalted-hash, or short-charset password issuance (external logins, magic links, temp accounts) is an offline-crackable credential family. Confirm the generator and hash, note the key space; do not crack real hashes in scope.

Durable operator value

  1. Decryption is not authentication. Any IPC/notify token that is decrypted and then used directly — without signature verification, freshness, one-use tracking, or issuer binding — is replayable by anyone who has ever seen a valid token (logs, client caches, sibling code paths). The reusable check: for every token-shaped input, list where it is validated vs merely parsed/decrypted.
  2. Notify endpoints are file-write primitives in disguise. Media/encoder platforms wire ingest callbacks into the filesystem (write the rendered frame, save the thumbnail, log the stat JSON). A caller-shaped path on any of those endpoints is arbitrary write → on PHP, .php write = RCE. Enumerate every filesystem sink behind a notify/IPC route and confirm the path is a fixed server-side constant.
  3. Hash-param lookups leak the whole object. When a "private" lookup key (hash, token, short ID) is the only gate on a record endpoint, and the record is a full user row, the key is a session-hijack + credential-theft primitive. Test the differential: valid-shaped key returns full record vs. absent key returns 404 — the full record is the finding.
  4. User-Agent is an attacker-controlled rate-limit bypass vector. Any limiter keyed on IP that special-cases crawler UAs converts bot traffic into a DoS-free brute-force channel. It is also a fingerprinting primitive: the platform's bot list is a disclosed allowlist.
  5. Callback-name dispatch is a client-side injection surface. Websocket/event-bus layers that route message fields into names (function names, handlers, templates) create a remote-dispatch primitive: an attacker who can emit socket messages executes named client code with attacker arguments.
  6. Cross-reference the AVideo batch pages. This batch extends the 2026-05-09 AVideo session/CSRF/SQL/race page (session fixation, admin JSON CSRF, wallet TOCTOU, RTMP SQLi, ReceiveImage traversal) and the 2026-05-15 AVideo auth/SSRF/command/transport page (SSRF out-param discard, 2FA-toggle CSRF, Meet filename impersonation, on_publish command injection). The recurring theme: AVideo puts state-changing and filesystem-touching endpoints behind different, weaker assumptions per route.

Replayable validation boundaries

All checks use a disposable AVideo install in a lab, synthetic accounts, marker files, and owned no-content peers. Stop at decision points; do not write real files outside the scratch root, do not hijack real sessions, and do not brute-force real accounts.

  1. Notify-code replay. Record one legitimately issued notifyCode (e.g., from a lab RTMP publish), then POST it to notify.ffmpeg.json.php with a synthetic avideoRelativePath pointing at a scratch marker path outside the media tree. A positive is the server accepting the replayed code and attempting the write; confirm the denial/acceptance decision at the sink. Do not write application root files.
  2. Views-info record shape. Call the videoViewsInfo endpoints with a synthetic valid-shaped hash. A positive is the response containing user-record fields (hash/recovery/session fields present in the response schema). Record the field list; do not exfiltrate or reuse live sessions.
  3. UA-gated limiter. Instrument the lab: fire N login attempts with a normal UA (expect 429/lockout), then repeat with each candidate bot UA. A positive is the lockout never firing under a bot UA while firing under the control UA.
  4. Socket callback dispatch. With YPTSocket enabled in the lab, connect a synthetic socket client and emit a message whose callback name targets a marker global (a harmless window.__marker function you define in the lab page that records the arguments). A positive is the marker firing with attacker-supplied arguments. Do not target real innerHTML sinks with script payloads; argument capture at the marker is the proof.
  5. Password key space. Identify the external-login password generator and hash in the source (lab build). Record the bit length and hash type as the finding; optionally recover the lab password offline to confirm crackability. No cracking of production dumps.

September 17 AVideo wave: hash-as-password auth bypass, CSRF-allowlist basename collisions, and CloneSite command injection

A second large AVideo batch landed in the September 17 wave (through 29.0, several confirmed at master HEAD with no patched version). The high-value members:

Advisory Boundary
GHSA-c9w6-444r-27pj / CVE-2026-92580 CloneSite plugin stores an SSH password then runs sshpass -p '{password}' rsync ... with plain str_replace and no escaping — a single quote in the password breaks out into arbitrary shell. The password is planted through an admin-only endpoint whose CSRF defense is a no-op when the request source appears to be loopback (same-host TLS-terminating reverse proxy with $global['trustedProxies'] unset, or a co-hosted attacker app), and on HTTPS the session cookie is SameSite=None, so a cross-site POST carries it. The plugin's documented crontab then executes the injected command as the crontab owner (commonly root) with no further admin action. Residual sink of CVE-2026-41304 — an incomplete-fix follow-up.
GHSA-hxf8-887x-mx5m / CVE-2026-92578 Password-hash pass-the-hash: loginFromRequest() and encryptPasswordVerify() each accept the stored users.password hash itself as valid credentials through two independent code paths — anyone holding a dumped/leaked hash authenticates without cracking it.
GHSA-4687-wm68-8h9w / CVE-2026-92579 autoCSRFGuard() exempts a hardcoded allowlist of basenames tested without directory context — a plugin file named login.json.php inherits the core exemption and unconditionally logs authenticated users out on cross-site POST before credential validation.
GHSA-52g5-jg96-428j / CVE-2026-92583 enforceRateLimit() race: the counter read/write is non-atomic, so concurrent requests defeat the limiter (brute-force channel; sibling of the Sept 5 bot-UA limiter bypass on this page).
GHSA-q24m-x4jf-vhrw / CVE-2026-92577 API get_api_video broken access control exposing video records. Siblings GHSA-2vmx-78wc-x2hc/GHSA-hgf3-hf6h-jm9r (video access-control verification gaps), GHSA-ghch-wqgj-8mcj (stored XSS), GHSA-x968-qj2r-r49p (CSRF), GHSA-rwxh-c5h7-gxqp (Like::__construct() counter arithmetic on raw request values before validation) round out the batch.

Operator checks this batch adds:

  1. Loopback-blind CSRF defenses on proxied appliances. isUntrustedRequest()-style checks that classify "remote addr == 127.0.0.1" as trusted are defeated by any same-host TLS terminator or co-hosted app. On any PHP app behind a proxy, test admin endpoints with a cross-site SameSite=None-carrying POST from a co-hosted origin; the trustedProxies-equivalent being unset is the enabling default.
  2. Crontab-executed stored payloads. Any admin-settable field that later feeds a scheduled command line (rsync/ssh/backup plugins) converts a one-time CSRF plant into persistent code execution at crontab privilege. Enumerate plugin fields → crontab entries; the injection point is the quoting of the stored value, not the endpoint auth.
  3. Pass-the-hash on login endpoints. Where a leak yields users.password hashes (view-stats hash-param leak on this page is one source), test submitting the hash itself as the password field before spending GPU time cracking — AVideo accepted it via two independent paths.
  4. Basename-scoped allowlists. Any security exemption keyed on filename-only (login.json.php, admin.php) is inheritable by plugins. Enumerate plugin directories for basename collisions with exempted core files.
  5. Fix-history re-checks. CVE-2026-92580 is explicitly a residual sink of CVE-2026-41304 — re-run the original command-injection matrix on sibling fields after any vendor fix, per the recurring incomplete-fix pattern.

All proofs stay in a disposable lab: synthetic admin session via owned co-hosted origin, marker shell payloads that only touch lab files, fake user rows for pass-the-hash, and concurrent-request instrumentation for the limiter race. No live-instance testing.

September 17 late wave: clock-derived pairing codes, a null-vs-null second factor, and an anonymous endpoint that mints recovery tokens

A third AVideo batch (published 2026-09-17T12:32Z) adds three credential-issuance boundaries. None has a patched release at wave time — treat "no patched version available" deployments as live-primitive territory for authorized lab work only.

  • GHSA-cv5x-3jv8-pgmr / CVE-2026-92913 (critical) — weak-PRNG login pairing codes are an offline-guessable one-year credential. getRandomCode() in objects/functions.php derives account-activation/login pairing codes entirely from uniqid() (sprintf('%08x%05x', seconds, microseconds)) with a single non-CSPRNG rand() character used only as padding. For a known generation second the code space collapses to ~36×10⁶ (~2²⁵) values, and codes live 10 minutes. Worse, plugin/API/set.json.php?APIName=login_code is callable without authentication and acts as an oracle for the server's exact microtime, pinning the generation second. Redeeming a valid code at plugin/API/get.json.php?APIName=login_code returns the target account's email plus a User::getUserHash(users_id, '+1 year') value — a credential accepted in place of the account password for one year → full account takeover.
  • Reusable checks: any "random" code built from clock material (uniqid, time+microtime, PID-seeded generators) is a key-space problem: derive the exact guessable space for a pinned timestamp and check whether an offline 2²⁵-guess fits inside the expiry window. An unauthenticated endpoint whose response embeds server timing (or whose latency/timing you can measure) is a key-space reduction oracle — pair timing measurement with weak-generation code flows. Long-lived derived credentials (hash-as-password, "remember for a year" tokens) turn a 10-minute race into persistent access: check the redemption response for what credential shape it hands back.
  • GHSA-x8jv-qqwm-22fj / CVE-2026-92914 (high) — PGP second-factor bypass via loose equality against an uninitialized session variable. LoginControl's second-factor step compares the challenge response to a session value with PHP loose equality (==); sending a parameter-less GET to verifyChallenge.json.php makes both sides null, and null == null marks authentication complete.
  • Reusable checks: on every MFA/2FA step, fire the verification endpoint with no parameters at all before testing payload logic — uninitialized-variable + == comparisons succeed on null-vs-null, 0 == "anything" (before PHP 8), and empty-string variants. The generalizable rule: attempt-state transitions must be gated on the presence and provenance of the answer, not on an equality that a request can make true by omission.
  • GHSA-wphj-4v3g-8q6c / CVE-2026-92915 (medium) — unauthenticated userVerifyEmail.php enumerates accounts and writes live password-recovery tokens onto any target. The script sets $global['ignoreUserMustBeLoggedIn'] = 1, takes users_id straight from the query string, and calls User::sendVerificationLink() with no session requirement, no CSRF/global token, no caller↔target relationship check, and no enforceRateLimit() — the only throttle keys the caller's own session, so cookie-less requests are unlimited. The three distinct JSON responses ("Verification Sent", "Already verified", "Unknown error") enumerate account IDs and their verification status. Hidden impact: createVerificationCode() calls $user->setRecoverPass() and saves, so each anonymous request writes a live password-recovery token onto the targeted account — the token ships base64-encoded inside the verification email and is accepted by objects/userRecoverPassSave.json.php as the set-new-password credential.
  • Reusable checks: ignoreUserMustBeLoggedIn-style flags make any AVideo object script an unauthenticated surface — enumerate every objects/*.php reachable directly, not just routes appearing in the UI. Any anonymous endpoint whose side effect is credential issuance (recovery/reset/verification token write) is an account-state attacker: response-shape differentials enumerate users, and unlimited issuance both spams and pre-loads tokens that leak through the email channel (phishing-adjacent token theft, link-forwarding). Test the differential on synthetic accounts only; never target real users' inboxes.

Durable axes this wave adds to the AVideo map: credential-issuance endpoints are attack surface even when their "action" is benign (send an email) because they mint tokens; PHP loose comparison remains the cheapest MFA bypass; and clock-derived randomness + an unauthenticated timing oracle converts an online-guess problem into an offline one. Cross-reference the Sept 17 morning fold above (pass-the-hash, basename-collision CSRF) — same platform, same theme: every route re-implements auth on its own weaker assumptions.

All proofs stay in a disposable lab: synthetic accounts and pairing codes, a lab NTP-skewed timing probe against your own instance, marker-only token writes; no live-instance guessing, no real account targeting, no redemption of real credentials.

October 4 18:3xZ follow-up: sanitizer run-order inversion and the trailer1 URL-into-JS-string sink

Two critical stored-XSS legs (published 2026-10-04T18:30Z) extend the AVideo map with two reusable sanitizer heuristics:

  • GHSA-6rvj-w837-rcgv / CVE-2026-105086 (critical) — authenticated uploaders store markup via doubly-encoded entities in video titles: safeString() strips tags before decoding entities, and the function runs twice (setTitle() then save()) — first pass decodes &amp;lt; → &lt;, second pass decodes &lt; → < after tag-stripping already ran. Executing in trending, gallery, embed, and playlist pages. Durable rules: (1) strip-then-decode is an inverted sanitizer — any sanitizer that removes tags before entity-decoding is defeatable with one encoding layer per application of the sanitizer; probe with &amp;lt;img, &amp;amp;lt;img, … matched to how many times the filter executes on the write path. (2) Count how many times the sanitizer runs, not just what it does — save-path + setter double-invocation turns a correct single-pass filter into a decode-after-strip pipeline. Trace your canary through the full write lifecycle (setter → store → re-read → render).
  • GHSA-m6h8-gp7c-3qmv / CVE-2026-105089 (critical) — upload-permission users inject script via a malicious video trailer1 URL: the value renders unescaped in YouPHPFlix2-template and channel-playlist contexts, breaking out of onclick= strings and iframe src= attributes. Durable rule: a field validated (or trusted) as a URL is still untrusted data in every template context it lands in — the same trailer value that is inert in an href becomes executable in an inline onclick JS-string context. Enumerate template/plugin variants (this one fires only in the YouPHPFlix2 plugin's templates — the feature-flag/template-family sweep pattern from the Sept 17 wave applies: toggle themes/plugins and re-run the stored-payload battery).

Cross-reference: sibling of the Sept 17 ignoreUserMustBeLoggedIn route-sweep finding — same platform, same theme of render-path escaping depending on template family. Validation: lab AVideo, synthetic uploader account, marker onerror payloads in disposable videos only.

Safety

  • Disposable lab AVideo instance only; synthetic users, marker files, lab tokens, owned no-content peers, denied real file/process/network sinks outside the lab.
  • No real session hijack, no real account brute-force, no real hash cracking, no writes outside the scratch root, no RCE exploitation on any host.
  • Report each primitive at its boundary (acceptance decision, response shape, limiter differential, marker callback firing) without performing the high-impact action on a live deployment.

Source: hourly offensive-security scan, 2026-09-05 (15:30 UTC GitHub advisory wave). Tracked in the source index.