
Plausible Analytics master d44e104 - Alta - Remoto - Privilegio residuo - editor people HTML perde trasferimento nanoid
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · plausible-editor-invite
Plausible Analytics community edition, master GitHub d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16) - Plausible Insights OÜ
Classe: privilegio residuo (leak del token di invito). Portata: remota (guest editor o team editor di quel sito).
Sites.list_people/1 seleziona direttamente guest_invitations.invitation_id e team_site_transfers.transfer_id. settings_people.html.heex inserisce quel nanoid di 21 caratteri in id="invitation-#{invitation.invitation_id}" per ogni riga in sospeso. Le impostazioni People non rientrano in @special_cased_actions di SiteController, quindi AuthorizeSiteAccess consente :editor. Il guest editor corrisponde al ruolo sito :editor. Il guest viewer restituisce 404.
Nanoid.generate/0 è il bearer per la registrazione da invito. La GET /register/invitation/:invitation_id non autenticata cerca solo per id. La LiveView register sovrascrive l'email inviata con invitation.email. La CE predefinita è DISABLE_REGISTRATION=invite_only e ENABLE_EMAIL_VERIFICATION=false. L'accettazione avviene tramite POST /sites/invitations/:id/accept con corrispondenza dell'email citext.
Un guest editor che può già vedere l'HTML di People legge un nanoid di trasferimento di proprietà in sospeso, si registra come invitato e accetta. Il sito passa al team personale dell'attaccante.
| ID | nessuna CVE al momento |
| CWE | CWE-200 / CWE-269 |
| CVSS | Alto: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L |
| Prodotto | Plausible Analytics |
| Affetto | master GitHub d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16). L'ultimo tag CE v3.2.1 (e4f5a87a5570bd61605217e7ceb376636db8eddb, 2026-05-15) è indietro di cinque mesi. SECURITY.md indica agli operatori di usare :latest. Il pin è l'HEAD corrente. La CE MIX_ENV=ce compila solo lib/. |
| Auth | guest editor autenticato (:guest_member, :editor) o team editor di quel sito. Non non autenticato. Non guest viewer. |
| Licenza | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18180 (http) 18181 (postgres) 18182 (clickhouse http) soltanto |
Residuo #1 di Plausible Analytics leftovers. Stesso pin, altro pacchetto: plausible-traffic-notify (mass-assignment di site_id in PUT traffic-change-notification). Questo pacchetto è quello che ruba un trasferimento di proprietà in sospeso.
Guest editor autenticato o team editor di un sito che ha un invito guest e/o un trasferimento di proprietà in sospeso. Serve un normale cookie di sessione. Nessun owner, nessun admin, nessuna LiveView super-admin. Non effettuano brute force sul nanoid di 21 caratteri. Lo leggono dall'HTML che sono autorizzati a caricare, poi si registrano come invitato.
Tabella dei privilegi (esaustiva). Stesso criterio di Signal-Server Dropwizard admin: metodo + percorso, cosa concede quella chiamata, recon vs integrità vs disponibilità, dimostrato in lab vs dedotto, superficie adiacente che resta chiusa.
| Asse | Metodo + percorso | Concede | Provenienza |
|---|---|---|---|
| Recon | GET /:domain/settings/people come guest/team editor | Le righe in sospeso renderizzano id="invitation-<nanoid>". Il nanoid è invitation_id o transfer_id. Solo stesso sito. L'email dell'invitato è anch'essa nella tabella. | Dimostrato in laboratorio. Editor people 200 ha fatto trapelare il nanoid Hc8DiBC2q08mv5LFmBysu |
| Recon | GET /register/invitation/:id non autenticato | Email dell'invitato nel form di registrazione. La ricerca avviene solo per id (find_by_id_unified). | Dimostrato in laboratorio. L'HTML di registrazione mostrava [email protected] |
| Integrità | LiveView register su /register/invitation/:id | Il user[email] inviato viene sovrascritto con invitation.email. Nuovo account CE all'indirizzo dell'invitato. Verifica email CE disattivata per default. Invite-only consente comunque :invitation. | Dimostrato in laboratorio. Inviato [email protected]; l'email memorizzata era [email protected] |
| Integrità | POST /sites/invitations/:id/accept come quel nuovo account | Il trasferimento di proprietà in sospeso sposta sites.team_id sul team personale del nuovo proprietario. La riga di trasferimento sparisce. | Dimostrato in laboratorio. sites.team_id 1 → 2 |
| Integrità | stesso accept su un invito guest in sospeso per quell'email | Nuova appartenenza a quel sito (editor-per-editor non è un guadagno). Anche altre righe team/guest/transfer in sospeso per quell'email diventano accettabili. | Sorgente (InvitationController.accept_invitation) |
| Disponibilità | dopo l'accept del trasferimento | Il team originale perde il sito. Le statistiche continuano a essere servite sul nuovo team. | Dimostrato in laboratorio. team_id spostato |
Resta chiuso su questo pin
| Asse | Metodo + percorso | Perché è chiuso |
|---|---|---|
| Recon | guest viewer GET /:domain/settings/people | 404. Il viewer non è :editor. Dimostrato in laboratorio. |
| Integrità | editor POST /sites/invitations/:id/accept dell'id di trasferimento trapelato | Corrispondenza email. L'editor non è [email protected]. Il sito resta sul team 1. Dimostrato in laboratorio. |
| Integrità | registrazione non autenticata senza un id di invito valido | check_registration_enabled(:default) vs :invitation. whoareme Bug 02, corretto. |
| Recon | brute force su nanoid da 21 caratteri | Alfabeto URL-safe, 21 caratteri. Fuori da questo residuo. |
| Recon | eventi ClickHouse, SMTP, pagine people di altri siti | Solo stesso sito. Nessun admin ClickHouse. Nessuna lettura della casella. |
| Integrità | invito e trasferimento owner/admin | Possono già farlo. Questo residuo riguarda l'editor che non può. |
| Integrità | LiveView super-admin, shell OS, extra/lib EE SSO | Chiuso. Lab CE. Nessuna shell. |
Non sto rivendicando un account takeover non autenticato di un utente già registrato, una reverse shell o un dump di ClickHouse. L'istanza High è un trasferimento owner in sospeso (o inviti verso altri siti/team per quell'email) su CE di default: invite-only più verifica email disattivata. EE imposta sempre email_verified: false e /activate richiede la casella dell'invitato.