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-chosenavideoRelativePathand writes the decryptednotifyCodepayload to it. ThenotifyCodeciphertext 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. videoViewsInfobroken access control — GHSA-hr26-p4g3-798m (critical 9.1, CWE-200): when ahashparameter 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-Agentheader 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
avideoConfirmHTMLwhose signatures assign untrusted data intoinnerHTML, 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¶
- 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.
notify.ffmpeg.json.phpis the headline: enumerate everynotify*/*.json.phpIPC 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?- For view-stat endpoints, test lookup keys (
hash,id,keyquery 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. - 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. - 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. - 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¶
- 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.
- 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,
.phpwrite = RCE. Enumerate every filesystem sink behind a notify/IPC route and confirm the path is a fixed server-side constant. - 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.
- 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.
- 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.
- 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_publishcommand 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.
- Notify-code replay. Record one legitimately issued
notifyCode(e.g., from a lab RTMP publish), then POST it tonotify.ffmpeg.json.phpwith a syntheticavideoRelativePathpointing 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. - Views-info record shape. Call the
videoViewsInfoendpoints with a synthetic valid-shapedhash. 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. - 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.
- 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.__markerfunction you define in the lab page that records the arguments). A positive is the marker firing with attacker-supplied arguments. Do not target realinnerHTMLsinks with script payloads; argument capture at the marker is the proof. - 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:
- 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-siteSameSite=None-carrying POST from a co-hosted origin; the trustedProxies-equivalent being unset is the enabling default. - 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.
- Pass-the-hash on login endpoints. Where a leak yields
users.passwordhashes (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. - 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. - 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()inobjects/functions.phpderives account-activation/login pairing codes entirely fromuniqid()(sprintf('%08x%05x', seconds, microseconds)) with a single non-CSPRNGrand()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_codeis callable without authentication and acts as an oracle for the server's exactmicrotime, pinning the generation second. Redeeming a valid code atplugin/API/get.json.php?APIName=login_codereturns the target account's email plus aUser::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 toverifyChallenge.json.phpmakes both sidesnull, andnull == nullmarks 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.phpenumerates accounts and writes live password-recovery tokens onto any target. The script sets$global['ignoreUserMustBeLoggedIn'] = 1, takesusers_idstraight from the query string, and callsUser::sendVerificationLink()with no session requirement, no CSRF/global token, no caller↔target relationship check, and noenforceRateLimit()— 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 byobjects/userRecoverPassSave.json.phpas the set-new-password credential. - Reusable checks:
ignoreUserMustBeLoggedIn-style flags make any AVideo object script an unauthenticated surface — enumerate everyobjects/*.phpreachable 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()thensave()) — first pass decodes&lt;→<, second pass decodes<→<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&lt;img,&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
trailer1URL: the value renders unescaped in YouPHPFlix2-template and channel-playlist contexts, breaking out ofonclick=strings andiframe 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 anhrefbecomes executable in an inlineonclickJS-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.