
Plausible Analytics master d44e104 - Alta - Remoto - IDOR - PUT traffic-change-notification site_id atribuição em massa
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · plausible-traffic-notify
Plausible Analytics edição comunitária, master do GitHub d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16) - Plausible Insights OÜ
Classe: IDOR (mass-assignment). Alcance: Remoto (proprietário / admin / editor de um site que o atacante já controla).
Plausible.Site.TrafficChangeNotification.changeset/2 ainda faz cast de :site_id. SiteController.update_traffic_change_notification/2 carrega a linha pelo domínio do caminho autorizado e, em seguida, faz Repo.update! do mapa do cliente. A rota PUT /sites/:domain/traffic-change-notification/:type está sob :browser + CSRF + AuthorizeSiteAccess [:owner, :admin, :editor, :super_admin]. O formulário HTML envia apenas threshold. O Phoenix ainda aceita chaves extras.
whoareme Bug 02 corrigiu Shield / Goal site_id da mesma forma: vincular socket.assigns.site.id, parar de fazer cast de :site_id. Este controller não estava nessa passagem. O enable (POST .../enable) grava site_id: site.id a partir dos assigns. O atacante habilita no próprio site e depois faz PUT de traffic_change_notification[site_id]=<victim_pk>. Índice único (site_id, type). A instalação padrão não tem linha de spike/drop, então a atualização é bem-sucedida e a linha agora pertence à vítima. O nome da tabela é legado: spike_notifications.
Um editor do próprio site re-parenteia a linha de notificação spike/drop para um site vítima. Os destinatários continuam sendo a caixa de e-mail do atacante. O Oban envia e-mails ao atacante sobre o tráfego da vítima.
| ID | ainda sem CVE |
| CWE | CWE-915 / CWE-639 |
| CVSS | Alto: 7.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
| Produto | Plausible Analytics |
| Afetado | master do GitHub d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16). A última tag CE v3.2.1 (e4f5a87a5570bd61605217e7ceb376636db8eddb, 2026-05-15) está cinco meses atrasada. CE + EE (lib/, não extra/lib). |
| Autenticação | proprietário / admin / editor de um site que controlam. Viewer não consegue. CSRF obrigatório (IDOR na mesma sessão). |
| Licença | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18183 (http) 18184 (postgres) 18185 (clickhouse http) apenas |
Sobra #2 de Plausible Analytics leftovers. Mesmo pin, outro pacote: plausible-editor-invite (o HTML de People vaza o nanoid de transferência para editores). Este pacote é o irmão Shield/Goal que ainda faz cast de :site_id.
Proprietário, admin ou editor de qualquer site que já controla. Precisa de um cookie de sessão e do token CSRF dessa sessão. Não precisa de associação na vítima. Não faz PUT no domínio do caminho da vítima (isso retorna 404). Faz PUT no próprio domínio com uma PK inteira site_id estrangeira.
Tabela de privilégios (exaustiva). Mesmo padrão de Signal-Server Dropwizard admin: método + caminho, o que essa chamada concede, reconhecimento vs integridade vs disponibilidade, comprovado em lab vs inferido, superfície adjacente que permanece fechada.
| Eixo | Método + caminho | Concede | Proveniência |
|---|---|---|---|
| Integridade | POST /sites/:attacker_domain/traffic-change-notification/:type/enable e depois PUT /sites/:attacker_domain/traffic-change-notification/:type com traffic_change_notification[site_id]=<victim_pk> | Reparenta a linha spike_notifications do atacante para victim.sites.id. Os destinatários continuam a ser o endereço do atacante. Passa a ser dono do slot único (site_id, type). O limiar é escolhido pelo atacante (o laboratório usou 1). | Comprovado em laboratório. A linha 1 moveu site_id de 2 → 1, destinatários {[email protected]} |
| Recon | E-mail do Oban TrafficChangeNotifier para notification.recipients | Assunto Traffic spike on <victim.domain>. Corpo: contagem atual de visitantes, 3 principais origens, 3 principais páginas. O primeiro envio é imediato (last_sent nil). Repetição no máximo a cada 12 horas. O limiar 1 gera um e-mail sempre que a vítima tenha qualquer visitante atual. O HTML do dashboard / links de instalação no e-mail são omitidos a menos que o destinatário seja membro de equipa/convidado (o atacante não é). | Inferido a partir do código-fonte do notificador. E-mail não capturado no laboratório (MAILER_ADAPTER=Bamboo.LocalAdapter) |
| Disponibilidade | POST .../enable da vítima do mesmo tipo | O único (site_id, type) rejeita. A vítima não consegue ativar a funcionalidade até a linha plantada desaparecer. | Código-fonte (unique_constraint) |
| Disponibilidade | disable da vítima (delete_all por site_id) | A vítima pode limpar a linha plantada se notar o destinatário extra. | Código-fonte (disable_traffic_change_notification) |
Permanece fechado neste pin
| Eixo | Método + caminho | Porque está fechado |
|---|---|---|
| Integridade | PUT /sites/:victim_domain/traffic-change-notification/:type | O domínio do caminho é AuthorizeSiteAccess. O atacante não é membro. Comprovado em laboratório 404. attacker-not-member-of-victim=1 |
| Integridade | Escrita em Shield / Goal / funnel / segment / shared-link | O whoareme Bug 02 corrigiu esses handlers cast :site_id. Eles vinculam assigns. |
| Recon | Dump do ClickHouse, Stats API, HTML do dashboard | Sem adesão, sem link partilhado. O e-mail do notificador omite links do dashboard para um não-membro. |
| Integridade | ingest do tracker | Já é POST /api/event sem autenticação com um domínio escolhido pelo cliente. Não é este resíduo. |
| Integridade | Repo.update! quando a vítima já tem esse tipo | 500 no único (site_id, type). A instalação padrão não tem linha, por isso a primeira plantação funciona. |
| Integridade | viewer do site do atacante | AuthorizeSiteAccess é owner/admin/editor. Viewer não consegue. |
| Integridade | shell do SO, LiveView de super-admin | Fechado. Sem shell. |
Não estou a reivindicar dump do ClickHouse, uma reverse shell, ou login no dashboard como a vítima. O dump é recon de tráfego no e-mail que o atacante já recebe: domínio, visitantes em direto, principais origens, principais páginas. site_id é o PK inteiro (enumerável; o whoareme usou site_id=1). O data-* do dashboard não o emite.
O whoareme Bug 02 ensinou o Shield e o Goal a deixar de fazer cast de :site_id. Fiz grep aos restantes cast(.*:site_id em lib/. A notificação de alteração de tráfego ainda o tinha. O enable carimba assigns. O update recebe o mapa do cliente. Esse é o resíduo.