KB-PLUGIN-044: visibilidade nativa em listagens de plugin + rights espelhados + guardas (hardening mindscrum 0.9.2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
63e650ed66
commit
2001dbfc0c
2 changed files with 126 additions and 0 deletions
22
index.json
22
index.json
|
|
@ -849,6 +849,28 @@
|
||||||
"severity": "medium",
|
"severity": "medium",
|
||||||
"path": "records/plugin-dev/KB-PLUGIN-043-glpi11-kanban-card-ux-patterns.md",
|
"path": "records/plugin-dev/KB-PLUGIN-043-glpi11-kanban-card-ux-patterns.md",
|
||||||
"summary": "id: KB-PLUGIN-043"
|
"summary": "id: KB-PLUGIN-043"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "KB-PLUGIN-044",
|
||||||
|
"title": "\"GLPI 11 — Visibilidade nativa em listagens de plugin (tickets/projetos) e permissionamento espelhado: receita e armadilhas\"",
|
||||||
|
"domain": "plugin-dev",
|
||||||
|
"tags": [
|
||||||
|
"glpi11",
|
||||||
|
"plugin",
|
||||||
|
"security",
|
||||||
|
"visibility",
|
||||||
|
"rights",
|
||||||
|
"profile",
|
||||||
|
"sqlprovider",
|
||||||
|
"ticket",
|
||||||
|
"project",
|
||||||
|
"hardening",
|
||||||
|
"mindscrum"
|
||||||
|
],
|
||||||
|
"status": "active",
|
||||||
|
"severity": "critical",
|
||||||
|
"path": "records/plugin-dev/KB-PLUGIN-044-glpi11-native-visibility-in-plugin-boards.md",
|
||||||
|
"summary": "id: KB-PLUGIN-044"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
---
|
||||||
|
id: KB-PLUGIN-044
|
||||||
|
title: "GLPI 11 — Visibilidade nativa em listagens de plugin (tickets/projetos) e permissionamento espelhado: receita e armadilhas"
|
||||||
|
domain: plugin-dev
|
||||||
|
tags:
|
||||||
|
- glpi11
|
||||||
|
- plugin
|
||||||
|
- security
|
||||||
|
- visibility
|
||||||
|
- rights
|
||||||
|
- profile
|
||||||
|
- sqlprovider
|
||||||
|
- ticket
|
||||||
|
- project
|
||||||
|
- hardening
|
||||||
|
- mindscrum
|
||||||
|
status: active
|
||||||
|
severity: critical
|
||||||
|
created_at: 2026-07-13
|
||||||
|
updated_at: 2026-07-13
|
||||||
|
applies_to:
|
||||||
|
- GLPI 11.0.8 dev
|
||||||
|
- plugin mindscrum 0.9.2
|
||||||
|
- qualquer plugin que liste tickets/projetos fora do Search nativo
|
||||||
|
related_records:
|
||||||
|
- KB-PLUGIN-028
|
||||||
|
- KB-PLUGIN-039
|
||||||
|
- KB-PLUGIN-043
|
||||||
|
---
|
||||||
|
|
||||||
|
# GLPI 11 — Visibilidade nativa em listagens de plugin e permissionamento espelhado
|
||||||
|
|
||||||
|
## O problema (achado em auditoria do mindscrum)
|
||||||
|
|
||||||
|
Um plugin que consulta `glpi_tickets`/`glpi_projects` direto com `$DB->request()`
|
||||||
|
e filtra só por `getEntitiesRestrictCriteria()` **vaza dados**: mostra a qualquer
|
||||||
|
usuário com o right do plugin TODOS os itens da entidade — ignorando as regras
|
||||||
|
nativas ("técnico vê só os seus/do grupo", "projeto visível a gerente/equipe").
|
||||||
|
O right do plugin NÃO substitui a visibilidade por item.
|
||||||
|
|
||||||
|
## Receita — tickets (a mesma do kanban do core)
|
||||||
|
|
||||||
|
`CommonITILObject::getDataToDisplayOnKanban` faz (replicar):
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Glpi\Search\Provider\SQLProvider;
|
||||||
|
|
||||||
|
$where = ['glpi_tickets.is_deleted' => 0] + getEntitiesRestrictCriteria('glpi_tickets');
|
||||||
|
$vis = SQLProvider::getDefaultWhereCriteria(\Ticket::class);
|
||||||
|
if ($vis !== []) {
|
||||||
|
$where[] = $vis;
|
||||||
|
}
|
||||||
|
$query = ['SELECT' => [/* campos PREFIXADOS glpi_tickets.x AS x */], 'FROM' => 'glpi_tickets', 'WHERE' => $where];
|
||||||
|
|
||||||
|
// ⚠️ o WHERE referencia JOINs com alias hasheado (glpi_tickets_users_<md5>...):
|
||||||
|
// SEM os joins correspondentes → "Unknown column ..._users_<hash>.users_id"
|
||||||
|
$linked = [];
|
||||||
|
$join = SQLProvider::getDefaultJoinCriteria(\Ticket::class, 'glpi_tickets', $linked);
|
||||||
|
if ($join !== []) {
|
||||||
|
$query = array_merge_recursive($query, $join);
|
||||||
|
}
|
||||||
|
// joins de atores DUPLICAM linhas → deduplicar por id ($rows[$r['id']] = $r)
|
||||||
|
```
|
||||||
|
|
||||||
|
Armadilhas: (1) `getDefaultWhereCriteria` sem `getDefaultJoinCriteria` = SQL
|
||||||
|
quebrado; (2) campos do SELECT precisam de prefixo de tabela (joins tornam `id`
|
||||||
|
ambíguo); (3) dedup por id obrigatório.
|
||||||
|
|
||||||
|
## Receita — projetos
|
||||||
|
|
||||||
|
```php
|
||||||
|
$vis = \Project::getVisibilityCriteria(); // ['LEFT JOIN'=>[], 'WHERE'=>[]]
|
||||||
|
$vis['WHERE'] += getEntitiesRestrictCriteria('glpi_projects', '', '', 'auto');
|
||||||
|
// LEFT JOIN => (seus joins) + $vis['LEFT JOIN']; WHERE => $vis['WHERE'] + (seus filtros)
|
||||||
|
// dedup por id (join com projectteams duplica)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Permissionamento espelhado (rights do plugin por perfil)
|
||||||
|
|
||||||
|
Conceder o right do plugin "para todos os perfis central" (mitigação C do
|
||||||
|
KB-PLUGIN-028) dá UPDATE até a Observer/Read-Only. Correto: espelhar o nativo —
|
||||||
|
`UPDATE` do plugin somente para perfis cujo right `ticket` tem `UPDATE`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$value = READ + (($ticket_rights & UPDATE) ? UPDATE : 0);
|
||||||
|
addDefaultProfileInfos($profile_id, [RIGHT => $value], true); // drop_existing corrige base
|
||||||
|
```
|
||||||
|
Reinstalar via console aplica a correção (install idempotente).
|
||||||
|
|
||||||
|
## Guardas de item-alvo (defesa em profundidade)
|
||||||
|
|
||||||
|
Right do plugin ≠ direito no item. TODA mutação deve carregar o alvo e delegar ao
|
||||||
|
nativo: `canUpdateItem()` (criar/alterar tarefa, mover, campos), `canViewItem()`
|
||||||
|
(comentar), `Ticket::canCreate()` + `canViewItem()` do projeto (spawn). Colocar as
|
||||||
|
guardas NAS CLASSES de serviço (não só no endpoint) — valem para qualquer chamador
|
||||||
|
e são testáveis em CLI.
|
||||||
|
|
||||||
|
Teste E2E que prova: sessão com `ticket => READ` (sem READALL) deve ver só os
|
||||||
|
chamados onde é ator; sessão com rights zerados deve ter toda mutação negada.
|
||||||
|
|
||||||
|
## Bônus — XSS de atributo em JS de plugin
|
||||||
|
|
||||||
|
`div.textContent = s; return div.innerHTML` escapa `& < >` mas **não aspas** —
|
||||||
|
inseguro para `title="..."`/`data-x="..."`. Usar replace de `[&<>"']`.
|
||||||
Loading…
Reference in a new issue